DEV Community

Cover image for How API Calls Actually Work: From Button Click to Server Response
Agrima Gupta
Agrima Gupta

Posted on

How API Calls Actually Work: From Button Click to Server Response

If you've worked with React, Next.js, or any frontend framework, you've probably written something like:

const response = await fetch("/api/users");
const data = await response.json();
Enter fullscreen mode Exit fullscreen mode

Looks simple, right?

But I used to wonder:

What actually happens after I call fetch()?

Where does the request go?

How does the backend know what I want?

Where does the database fit in?

And how does the response come back to my UI?

Let's break down the entire journey of one API call.


The Big Picture

Imagine we have a button:

<button onclick="getUsers()">
  Get Users
</button>
Enter fullscreen mode Exit fullscreen mode

When the user clicks it, the flow looks roughly like this:

Button Click
     ↓
Frontend JavaScript
     ↓
HTTP Request
     ↓
Backend API
     ↓
Database
     ↓
Backend Response
     ↓
Frontend
     ↓
UI Update
Enter fullscreen mode Exit fullscreen mode

That's it.

Now let's see what's actually happening at each step.


1. The Button Click

Let's say we want to fetch a list of users.

async function getUsers() {

    const response = await fetch(
        "http://localhost:8000/api/users"
    );

    const data = await response.json();

    console.log(data);
}
Enter fullscreen mode Exit fullscreen mode

The important line is:

fetch("http://localhost:8000/api/users")
Enter fullscreen mode Exit fullscreen mode

We're basically telling the browser:

"Hey, send a request to this API and give me whatever comes back."


2. The Browser Sends an HTTP Request

Behind that simple fetch() call, the browser creates an HTTP request.

Something like:

GET /api/users
Enter fullscreen mode Exit fullscreen mode

GET is the HTTP method.

Some common methods are:

GET     → Get data
POST    → Create data
PUT     → Replace data
PATCH   → Update data
DELETE  → Delete data
Enter fullscreen mode Exit fullscreen mode

So:

GET /api/users
Enter fullscreen mode Exit fullscreen mode

basically means:

"Give me the users."


3. The Backend Receives It

Now imagine our backend is built using FastAPI.

from fastapi import FastAPI

app = FastAPI()

@app.get("/api/users")
def get_users():

    return [
        {"id": 1, "name": "Agrima"},
        {"id": 2, "name": "Rahul"}
    ]
Enter fullscreen mode Exit fullscreen mode

This line:

@app.get("/api/users")
Enter fullscreen mode Exit fullscreen mode

is basically telling the backend:

"Whenever someone sends a GET request to /api/users, run this function."

So our request:

GET /api/users
Enter fullscreen mode Exit fullscreen mode

finds this function:

get_users()
Enter fullscreen mode Exit fullscreen mode

4. What About the Database?

In a real application, we're obviously not going to hardcode users.

The backend would probably query a database.

For example:

SELECT * FROM users;
Enter fullscreen mode Exit fullscreen mode

The database might return:

1 | Agrima
2 | Rahul
3 | Priya
Enter fullscreen mode Exit fullscreen mode

The backend then converts this into a response, usually JSON:

[
  {
    "id": 1,
    "name": "Agrima"
  },
  {
    "id": 2,
    "name": "Rahul"
  }
]
Enter fullscreen mode Exit fullscreen mode

So now our flow becomes:

Frontend
   ↓
API Request
   ↓
Backend
   ↓
Database
   ↓
Backend
Enter fullscreen mode Exit fullscreen mode

5. The Response Comes Back

The backend sends an HTTP response.

For example:

200 OK
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

with:

[
  {
    "id": 1,
    "name": "Agrima"
  },
  {
    "id": 2,
    "name": "Rahul"
  }
]
Enter fullscreen mode Exit fullscreen mode

And this is where our frontend gets the data.

Remember this?

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

Now we do:

const data = await response.json();
Enter fullscreen mode Exit fullscreen mode

And data becomes:

[
    { id: 1, name: "Agrima" },
    { id: 2, name: "Rahul" }
]
Enter fullscreen mode Exit fullscreen mode

We can finally use it in our UI.


6. What Does await Actually Do?

This was another thing that confused me when I started.

An API request takes time.

Maybe:

50ms
200ms
1 second
Enter fullscreen mode Exit fullscreen mode

JavaScript can't just assume the response will instantly arrive.

That's why we use:

await fetch(...)
Enter fullscreen mode Exit fullscreen mode

We're basically saying:

"Send the request and wait for the response before continuing this function."

That's why API calls are commonly written with:

async/await
Enter fullscreen mode Exit fullscreen mode

7. What If the API Fails?

Things go wrong. A lot.

Maybe the server is down.

Maybe the URL is wrong.

Maybe the user isn't authenticated.

Maybe the backend crashes.

So don't write API calls assuming everything will always work.

A basic pattern is:

async function getUsers() {

    try {

        const response =
            await fetch("/api/users");

        if (!response.ok) {
            throw new Error(
                `Request failed: ${response.status}`
            );
        }

        const data =
            await response.json();

        console.log(data);

    } catch (error) {

        console.error(
            "Something went wrong:",
            error
        );

    }
}
Enter fullscreen mode Exit fullscreen mode

Some status codes you'll see often:

200 → Success
201 → Created
400 → Bad Request
401 → Unauthorized
403 → Forbidden
404 → Not Found
500 → Server Error
Enter fullscreen mode Exit fullscreen mode

The Complete Flow

So that tiny line:

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

actually triggers a much bigger process:

        USER
          │
          ↓
    Clicks Button
          │
          ↓
     JavaScript
          │
          ↓
     fetch()
          │
          ↓
    HTTP Request
          │
          ↓
      Backend
          │
          ↓
      Database
          │
          ↓
      Backend
          │
          ↓
    HTTP Response
          │
          ↓
     JavaScript
          │
          ↓
       UI Update
Enter fullscreen mode Exit fullscreen mode

And that's basically the request-response cycle.


One More Real-World Example

Think about an AI notes app.

You type:

"Summarize these notes"

and click Summarize.

Behind the scenes:

You click "Summarize"
        ↓
Next.js frontend
        ↓
POST /api/summarize
        ↓
Backend
        ↓
AI API
        ↓
AI generates summary
        ↓
Backend sends response
        ↓
Frontend receives it
        ↓
Summary appears on screen
Enter fullscreen mode Exit fullscreen mode

You only see a button and a result.

But underneath, multiple systems are talking to each other.

That's the part I find most interesting about APIs.


Final Takeaway

An API call isn't just:

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

It's a conversation between different parts of an application.

Client → Request → Server → Process → Response → Client
Enter fullscreen mode Exit fullscreen mode

Once you understand this flow, things like REST APIs, Axios, FastAPI, Express, authentication, CORS, and even AI APIs start making a lot more sense.

So next time you write:

await fetch(...)
Enter fullscreen mode Exit fullscreen mode

you'll know what's actually happening behind that one line. 🚀


If you're learning APIs right now, try this: build a tiny frontend + backend with just three endpoints:

GET    /users
POST   /users
DELETE /users/:id
Enter fullscreen mode Exit fullscreen mode

You'll learn more by building those three endpoints than by reading ten API tutorials.

Top comments (0)