How a backend is built
request, handler, response
What it is
A backend is a program that waits for requests and answers them. Inside, it is always built the same way, in any language and any framework: a request arrives, the router decides who will handle it, the handler does the work, the response goes back.
Understand that chain once and you can find your way around any backend project — the words and the syntax change, the scheme stays.
What it looks like in code
Here is that scheme in code. A working example on FastAPI:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI()
SUBS: dict[int, list[str]] = {7: ["Oslo", "Bergen"]}
class SubOut(BaseModel):
user_id: int
cities: list[str]
@app.get("/subs/{user_id}", response_model=SubOut)
def get_subs(user_id: int) -> SubOut:
if user_id not in SUBS:
raise HTTPException(status_code=404, detail="user not found")
return SubOut(user_id=user_id, cities=SUBS[user_id])The request and the response, for real:
curl -i http://127.0.0.1:8765/subs/7HTTP/1.1 200 OK
server: uvicorn
content-length: 40
content-type: application/json
{"user_id":7,"cities":["Oslo","Bergen"]}curl -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8765/subs/999404How to read it
@app.get("/subs/{user_id}") is the route. The decorator registers the
function: "call it on a GET request to this address" (article 2.9). Looking
for a call to get_subs in the project is pointless — the framework calls
it.
user_id: int is part of the address turned into an argument. The
framework pulls it out of /subs/7 and converts it to a number. Send
/subs/abc and the handler never even starts — a type error comes back
instead.
response_model=SubOut is the shape of the response. The framework
checks that this is what was returned and turns it into JSON.
raise HTTPException(404) is how you answer with an error. Not return
but raise: the framework catches the exception and turns it into an HTTP
response (article 2.8).
What the framework does and what you do
This is the key to reading other people's code:
| The framework | You |
|---|---|
| accepts the connection | decide what to do with the data |
| parses the URL and finds the handler | go to the database |
| checks types and schemas | decide what counts as an error |
| turns the result into JSON | |
| catches exceptions and returns codes |
Your code is one link in the chain. Which is why a backend project has many files, and the handlers are what you read first.
What to tell the AI
Show every route in this project: method, address and the
function that handles it. As a table, no code.What happens on GET /subs/abc? Does the handler run, or does
the error come earlier?Add a handler GET /subs/{user_id}/count that returns the number
of subscriptions. 404 if the user doesn't exist. Don't touch
the other routes.What to look for
Logic inside the handler. A handler should accept the request, call what is needed and return the result. If it holds fifty lines of SQL and business rules, that code cannot be reused or tested later.
Database access right in the handler. The same thing: queries are moved into a separate module so they can change without touching the routes.
A response with no schema. Without response_model the framework will
return anything at all, including fields the user should not see.
return instead of raise on an error. Returning {"error": "not found"} with a 200 is lying to the client: formally everything is fine.
Cheat sheet
The chain is the same everywhere: client → route → handler → database → response.
Your code is only the handler; the framework does the rest.
A route is registered by a decorator; the call won't be found in the project.
An error is returned with
raise, notreturn.Read an unfamiliar backend from the list of routes — it is its contents page.
Logic and database queries are moved out of the handler.