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();
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>
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
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);
}
The important line is:
fetch("http://localhost:8000/api/users")
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
GET is the HTTP method.
Some common methods are:
GET → Get data
POST → Create data
PUT → Replace data
PATCH → Update data
DELETE → Delete data
So:
GET /api/users
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"}
]
This line:
@app.get("/api/users")
is basically telling the backend:
"Whenever someone sends a GET request to
/api/users, run this function."
So our request:
GET /api/users
finds this function:
get_users()
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;
The database might return:
1 | Agrima
2 | Rahul
3 | Priya
The backend then converts this into a response, usually JSON:
[
{
"id": 1,
"name": "Agrima"
},
{
"id": 2,
"name": "Rahul"
}
]
So now our flow becomes:
Frontend
↓
API Request
↓
Backend
↓
Database
↓
Backend
5. The Response Comes Back
The backend sends an HTTP response.
For example:
200 OK
Content-Type: application/json
with:
[
{
"id": 1,
"name": "Agrima"
},
{
"id": 2,
"name": "Rahul"
}
]
And this is where our frontend gets the data.
Remember this?
const response = await fetch("/api/users");
Now we do:
const data = await response.json();
And data becomes:
[
{ id: 1, name: "Agrima" },
{ id: 2, name: "Rahul" }
]
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
JavaScript can't just assume the response will instantly arrive.
That's why we use:
await fetch(...)
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
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
);
}
}
Some status codes you'll see often:
200 → Success
201 → Created
400 → Bad Request
401 → Unauthorized
403 → Forbidden
404 → Not Found
500 → Server Error
The Complete Flow
So that tiny line:
fetch("/api/users")
actually triggers a much bigger process:
USER
│
↓
Clicks Button
│
↓
JavaScript
│
↓
fetch()
│
↓
HTTP Request
│
↓
Backend
│
↓
Database
│
↓
Backend
│
↓
HTTP Response
│
↓
JavaScript
│
↓
UI Update
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
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");
It's a conversation between different parts of an application.
Client → Request → Server → Process → Response → Client
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(...)
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
You'll learn more by building those three endpoints than by reading ten API tutorials.
Top comments (0)