DEV Community

Cover image for What Actually Happens When You Call an API?
Tanu Priya
Tanu Priya

Posted on

What Actually Happens When You Call an API?

You write:

fetch("/api/users");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

the browser first resolves the relative URL against the current page.

For example, if you're currently on:

https://example.com/dashboard
Enter fullscreen mode Exit fullscreen mode

the request URL becomes:

https://example.com/api/user/123
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For example:

93.184.216.34
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

With HTTPS, TLS is then established over that connection:

Browser
   ↓
TCP connection
   ↓
TLS handshake
   ↓
Secure connection
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The request can contain several things.

Method

GET
Enter fullscreen mode Exit fullscreen mode

Common HTTP methods include:

GET
POST
PUT
PATCH
DELETE
Enter fullscreen mode Exit fullscreen mode

URL

/api/user/123
Enter fullscreen mode Exit fullscreen mode

Headers

Authorization: Bearer ...
Content-Type: application/json
Accept: application/json
Enter fullscreen mode Exit fullscreen mode

Body

Some requests also contain data.

For example:

POST /api/users
Content-Type: application/json

{
  "name": "Alex",
  "email": "alex@example.com"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

There could also be:

WAF
API Gateway
Rate Limiter
Authentication Middleware
Enter fullscreen mode Exit fullscreen mode

depending on how the application is deployed.

For a small application, the architecture might be much simpler:

Browser
   ↓
Server
   ↓
Application
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and compares it against registered routes.

For example:

app.get("/api/user/:id", getUser);
Enter fullscreen mode Exit fullscreen mode

The router recognizes:

/api/user/123
Enter fullscreen mode Exit fullscreen mode

as:

/api/user/:id
Enter fullscreen mode Exit fullscreen mode

and extracts:

id = 123
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

If authentication fails:

Request
   ↓
Authentication
   ↓
❌ Unauthorized
Enter fullscreen mode Exit fullscreen mode

The controller may never run.

If authentication succeeds:

Request
   ↓
Authentication
   ↓
Controller
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The controller could call a service:

const order = await orderService.createOrder(userId, items);
Enter fullscreen mode Exit fullscreen mode

The service might then:

  1. Check whether the user exists
  2. Validate the products
  3. Check inventory
  4. Calculate the total
  5. Apply discounts
  6. Create the order
  7. 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
  }
});
Enter fullscreen mode Exit fullscreen mode

The application sends a query to the database.

Conceptually:

Backend
   ↓
Database Query
   ↓
Database
   ↓
Result
   ↓
Backend
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

Notice the status code:

200 OK
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Now fetch() has something to give us.

The response object contains information about the HTTP response.

For example:

console.log(response.status);
Enter fullscreen mode Exit fullscreen mode

might produce:

200
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Now we get:

{
  id: 123,
  name: "Alex",
  email: "alex@example.com"
}
Enter fullscreen mode Exit fullscreen mode

17. The UI Updates

Finally, the frontend can use the data.

For example:

setUser(user);
Enter fullscreen mode Exit fullscreen mode

React may then re-render the component.

The user sees:

┌─────────────────────────┐
│ Alex                    │
│ alex@example.com        │
└─────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

And all of this started from:

fetch("/api/user/123");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

but the user doesn't exist.

The backend might return:

404 Not Found
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Or maybe the server crashes.

You could receive:

500 Internal Server Error
Enter fullscreen mode Exit fullscreen mode

Or your authentication token might be invalid:

401 Unauthorized
Enter fullscreen mode Exit fullscreen mode

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    │
└───────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

GraphQL

query {
  user(id: 123) {
    name
    email
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Think:

Frontend
   ↓
URL resolution
   ↓
DNS
   ↓
Connection
   ↓
TLS / HTTPS
   ↓
HTTP Request
   ↓
Server
   ↓
Router
   ↓
Middleware
   ↓
Controller
   ↓
Business Logic
   ↓
Database
   ↓
HTTP Response
   ↓
Frontend
Enter fullscreen mode Exit fullscreen mode

That mental model will make backend development much easier to understand.

The next time you write:

await fetch("/api/users");
Enter fullscreen mode Exit fullscreen mode

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)