You write:
fetch("/api/users");
A few milliseconds later, data appears on your screen.
It feels simple.
But behind that single line, a lot is happening.
Your frontend has to create an HTTP request, determine where to send it, establish the necessary network connection, securely communicate with the server, reach the correct route, execute backend logic, possibly query a database, create a response, send it back, and finally let the frontend process that response.
The complete journey looks something like this:
Frontend
↓
fetch()
↓
URL / Request Preparation
↓
DNS Lookup (if needed)
↓
Connection Setup
↓
TLS Encryption (HTTPS)
↓
HTTP Request
↓
Server / Infrastructure
↓
Route
↓
Controller
↓
Business Logic
↓
Database
↓
HTTP Response
↓
Frontend
Let's follow that journey step by step.
1. It Starts With fetch()
Imagine you have a page that needs to display a user's profile.
Your frontend might contain:
const response = await fetch("/api/user/123");
const user = await response.json();
console.log(user);
At first glance, it looks like we're simply asking the server for some data.
But fetch() is actually initiating a request.
Conceptually, the HTTP request might look like:
GET /api/user/123 HTTP/1.1
Host: example.com
Accept: application/json
There are several important pieces here:
-
GET→ the HTTP method -
/api/user/123→ the requested resource -
Host→ the destination host -
Accept→ what kind of response the client expects
The browser takes care of constructing much of this for you.
2. The Browser Resolves The URL
If you're calling:
fetch("/api/user/123");
the browser first resolves the relative URL against the current page.
For example, if you're currently on:
https://example.com/dashboard
the request URL becomes:
https://example.com/api/user/123
Now the browser knows:
- The protocol is
HTTPS - The hostname is
example.com - The path is
/api/user/123
Before the browser can communicate with example.com, it may need to determine the server's IP address.
3. DNS Finds The IP Address
Humans use domain names:
example.com
Networks ultimately need an IP address to locate the destination.
So the browser or operating system may perform a DNS lookup:
example.com
↓
DNS
↓
IP address
For example:
93.184.216.34
DNS stands for Domain Name System.
You can think of DNS as a directory that helps map domain names to IP addresses.
However, DNS doesn't necessarily happen for every request.
The result may already be cached by:
- The browser
- The operating system
- A local DNS resolver
- Another layer of the network
So sometimes the browser can skip a new DNS lookup.
4. The Browser Establishes A Connection
Once the destination is known, the browser needs a way to communicate with the server.
The exact process depends on the HTTP version and protocol being used.
For example, with HTTP/1.1 or HTTP/2, communication commonly uses TCP.
Conceptually:
Browser
↓
TCP connection
↓
Server
With HTTPS, TLS is then established over that connection:
Browser
↓
TCP connection
↓
TLS handshake
↓
Secure connection
For HTTP/3, things work differently.
HTTP/3 uses QUIC, which runs over UDP and incorporates the TLS handshake into the QUIC connection process.
So the exact networking details can vary.
For a beginner, the important idea is:
Before the HTTP request can be exchanged securely, the browser and server need to establish the appropriate connection and security context.
5. HTTPS Secures The Communication
Now we get to HTTPS.
HTTPS is essentially HTTP carried over a secure TLS connection.
TLS stands for Transport Layer Security.
During the TLS handshake, the browser and server establish cryptographic parameters that allow them to communicate securely.
The result is a connection where the HTTP data is encrypted in transit.
Conceptually:
Browser
↓
Encrypted connection
↓
Server
This helps protect the request and response from being read or modified by someone who intercepts the network traffic.
For example, instead of someone on the network being able to simply read:
Authorization: Bearer my-token
the HTTP data is transmitted through the encrypted TLS connection.
6. The HTTP Request Is Sent
Once the appropriate connection is ready, the browser can send the HTTP request.
For example:
GET /api/user/123 HTTP/1.1
Host: example.com
Authorization: Bearer <token>
Accept: application/json
The request can contain several things.
Method
GET
Common HTTP methods include:
GET
POST
PUT
PATCH
DELETE
URL
/api/user/123
Headers
Authorization: Bearer ...
Content-Type: application/json
Accept: application/json
Body
Some requests also contain data.
For example:
POST /api/users
Content-Type: application/json
{
"name": "Alex",
"email": "alex@example.com"
}
A GET request typically doesn't need a request body, while POST, PUT, and PATCH commonly do.
7. The Request Reaches Your Infrastructure
The request may not go directly from the internet to your application.
Modern applications often have several layers.
For example:
Browser
↓
DNS
↓
CDN / Load Balancer
↓
Reverse Proxy / Web Server
↓
Application Server
There could also be:
WAF
API Gateway
Rate Limiter
Authentication Middleware
depending on how the application is deployed.
For a small application, the architecture might be much simpler:
Browser
↓
Server
↓
Application
For a large production system, there may be many services between the browser and the code you wrote.
Also, the TLS connection may terminate at infrastructure such as a CDN, load balancer, or reverse proxy rather than directly at your application process.
8. The Server Receives The Request
Eventually, the request reaches the component responsible for your backend application.
Suppose you're using Node.js and Express.
Your server might have:
app.get("/api/user/:id", getUser);
The backend now has to figure out:
"Which piece of code should handle
/api/user/123?"
This is where routing comes in.
9. The Router Matches The URL
The server examines the request.
It sees:
GET /api/user/123
and compares it against registered routes.
For example:
app.get("/api/user/:id", getUser);
The router recognizes:
/api/user/123
as:
/api/user/:id
and extracts:
id = 123
Now the request can be passed to the appropriate handler.
10. Middleware May Run Before The Controller
Before your controller executes, the request may pass through middleware.
For example:
app.use(authenticateUser);
Middleware can perform tasks such as:
- Authentication
- Authorization
- Logging
- Validation
- Rate limiting
- Parsing request bodies
- Adding information to the request
For example:
function authenticateUser(req, res, next) {
const token = req.headers.authorization;
// Validate token...
next();
}
If authentication fails:
Request
↓
Authentication
↓
❌ Unauthorized
The controller may never run.
If authentication succeeds:
Request
↓
Authentication
↓
Controller
11. The Controller Handles The Request
Now we reach the controller.
For example:
async function getUser(req, res) {
const user = await User.findById(req.params.id);
res.json(user);
}
The controller receives the request and decides what needs to happen.
But controllers usually shouldn't contain every piece of application logic.
A larger application might use:
Route
↓
Middleware
↓
Controller
↓
Service
↓
Repository
↓
Database
This separation makes backend applications easier to maintain.
12. Business Logic Happens
Suppose we're building an e-commerce application.
The request might be:
POST /api/orders
The controller could call a service:
const order = await orderService.createOrder(userId, items);
The service might then:
- Check whether the user exists
- Validate the products
- Check inventory
- Calculate the total
- Apply discounts
- Create the order
- Update inventory
This is business logic.
The API isn't simply moving data around.
It's often enforcing the rules of the application.
13. The Backend May Talk To A Database
If the request needs data, the backend may query a database.
For example:
const user = await db.users.findUnique({
where: {
id: 123
}
});
The application sends a query to the database.
Conceptually:
Backend
↓
Database Query
↓
Database
↓
Result
↓
Backend
The database might be:
- PostgreSQL
- MySQL
- MongoDB
- SQLite
- Redis
- DynamoDB
- Or another database system
For our example, the operation could be conceptually similar to:
SELECT *
FROM users
WHERE id = 123;
The database returns the matching record.
14. The Backend Builds The Response
Now the server has the data it needs.
It needs to send something back to the client.
For example:
{
"id": 123,
"name": "Alex",
"email": "alex@example.com"
}
The backend sends this as an HTTP response.
A response might look like:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Alex",
"email": "alex@example.com"
}
Notice the status code:
200 OK
HTTP status codes tell the client what happened.
Some common ones are:
200 → Success
201 → Created
400 → Bad Request
401 → Unauthorized
403 → Forbidden
404 → Not Found
500 → Internal Server Error
15. The Response Travels Back
Now the HTTP response travels back to the browser through the established network connection.
Conceptually:
Database
↓
Backend
↓
Server / Infrastructure
↓
Encrypted network connection
↓
Browser
If HTTPS is being used, the HTTP response is protected by TLS while it is in transit.
The browser receives the response and makes its contents available to JavaScript.
16. fetch() Resolves
Remember our original code?
const response = await fetch("/api/user/123");
Now fetch() has something to give us.
The response object contains information about the HTTP response.
For example:
console.log(response.status);
might produce:
200
But there's an important detail.
The response body hasn't automatically become a JavaScript object.
We still need to parse it.
const user = await response.json();
Now we get:
{
id: 123,
name: "Alex",
email: "alex@example.com"
}
17. The UI Updates
Finally, the frontend can use the data.
For example:
setUser(user);
React may then re-render the component.
The user sees:
┌─────────────────────────┐
│ Alex │
│ alex@example.com │
└─────────────────────────┘
And all of this started from:
fetch("/api/user/123");
The Complete Journey
Let's put everything together.
User interacts with UI
↓
Frontend calls fetch()
↓
URL is resolved
↓
DNS lookup (if needed)
↓
Connection is established
↓
TLS secures the connection (HTTPS)
↓
HTTP request is sent
↓
CDN / Load Balancer / Proxy
↓
Backend server
↓
Router matches endpoint
↓
Middleware
↓
Controller
↓
Business Logic
↓
Database
↓
Database Response
↓
Controller creates HTTP Response
↓
Response travels back
↓
Browser receives response
↓
fetch() resolves
↓
response.json()
↓
Frontend state updates
↓
UI re-renders
A small but important networking note
The exact connection steps depend on the protocol.
For example:
HTTP/1.1 → commonly TCP + TLS
HTTP/2 → commonly TCP + TLS
HTTP/3 → QUIC over UDP + TLS
So don't memorize "DNS → TCP → TLS → HTTP" as a universal rule.
Instead, remember the bigger picture:
Find the destination → establish the appropriate connection → secure it when using HTTPS → exchange HTTP data.
One line of JavaScript can trigger this entire chain.
What If Something Goes Wrong?
This is where HTTP status codes become extremely useful.
Imagine requesting:
GET /api/user/999
but the user doesn't exist.
The backend might return:
404 Not Found
Your frontend can handle that:
const response = await fetch("/api/user/999");
if (!response.ok) {
throw new Error("User not found");
}
const user = await response.json();
Or maybe the server crashes.
You could receive:
500 Internal Server Error
Or your authentication token might be invalid:
401 Unauthorized
The API isn't just returning data.
It's communicating the result of an operation.
Why APIs Are So Important
An API creates a boundary between different parts of an application.
For example:
┌───────────────┐
│ Frontend │
└───────┬───────┘
│
│ HTTP
↓
┌───────────────┐
│ Backend API │
└───────┬───────┘
│
↓
┌───────────────┐
│ Database │
└───────────────┘
The frontend doesn't need to know how the database works.
It only needs to know how to communicate with the API.
That separation allows teams to build different parts of a system independently.
One More Important Detail: APIs Don't Have To Be REST
When developers hear "API", they often immediately think about REST.
But APIs can use different approaches.
For example:
REST
GET /users/123
GraphQL
query {
user(id: 123) {
name
email
}
}
WebSockets
Instead of repeatedly sending HTTP requests, the client and server can maintain a persistent connection for real-time communication.
This is useful for:
- Chat applications
- Multiplayer games
- Live dashboards
- Notifications
- Real-time collaboration
The underlying communication model changes, but the core idea remains:
Different parts of a system need a way to communicate.
The Mental Model To Remember
When you call an API, don't think:
fetch()
↓
data
Think:
Frontend
↓
URL resolution
↓
DNS
↓
Connection
↓
TLS / HTTPS
↓
HTTP Request
↓
Server
↓
Router
↓
Middleware
↓
Controller
↓
Business Logic
↓
Database
↓
HTTP Response
↓
Frontend
That mental model will make backend development much easier to understand.
The next time you write:
await fetch("/api/users");
remember that you're not simply "getting some data."
You're starting a complete request-response journey between your application and another system.
And that journey is the foundation of modern web applications.
Top comments (0)