fastapi/fastapi
Request lifecycle
End-to-end view of what happens between an inbound HTTP request and the outbound response. The same shape applies to WebSockets with one substitution noted at the end.
High-level flow
sequenceDiagram
participant Client
participant Server as ASGI server (uvicorn)
participant SE as Starlette ServerError
participant UM as User middleware (CORS, etc.)
participant EX as Exception middleware (Starlette)
participant AS as AsyncExitStackMiddleware<br/>fastapi/middleware/asyncexitstack.py
participant RT as APIRoute<br/>fastapi/routing.py
participant DR as solve_dependencies<br/>fastapi/dependencies/utils.py
participant FN as Endpoint function
participant SR as serialize_response<br/>fastapi/routing.py
Client->>Server: HTTP request
Server->>SE: scope, receive, send
SE->>UM: forward
UM->>EX: forward
EX->>AS: forward
AS->>AS: open scope["fastapi_middleware_astack"]
AS->>RT: route match
RT->>RT: open scope["fastapi_inner_astack"]
RT->>RT: open scope["fastapi_function_astack"]
RT->>DR: dependant tree + request
DR->>DR: parse path/query/header/cookie/body
DR->>DR: resolve sub-dependencies (with cache)
DR-->>RT: kwargs, errors
alt validation errors
RT->>EX: raise RequestValidationError
EX-->>Client: 422 JSON
else
RT->>FN: endpoint(**kwargs)
FN-->>RT: return value
RT->>SR: validate against response_model, dump
SR-->>RT: Response
RT-->>Client: response
RT->>RT: close function_astack (run yield-cleanup)
RT->>RT: close inner_astack
AS->>AS: close middleware_astack (close UploadFiles, …)
endPhases in detail
1. Middleware stack
FastAPI.__init__ builds the Starlette middleware stack in this order (outermost first):
ServerErrorMiddleware(Starlette) — catches uncaught exceptions and produces a 500.- User middleware added through
app.add_middleware(...)—CORSMiddleware,GZipMiddleware, custom auth, etc. ExceptionMiddleware(Starlette) — translatesHTTPException(and registered custom exceptions) into responses.AsyncExitStackMiddleware(FastAPI) — opensscope["fastapi_middleware_astack"].
AsyncExitStackMiddleware is added inside FastAPI.__init__ after user middlewares, so user middleware sees the request before the framework opens its outer cleanup stack.
2. Route match
The APIRouter (a starlette.routing.Router) walks the route table and finds the APIRoute that matches the request path and method. For OPTIONS, HEAD, TRACE, etc., FastAPI behaves like Starlette unless the user explicitly registered a route for that method.
3. Inner exit stacks
Inside request_response() in fastapi/routing.py:
async with AsyncExitStack() as request_stack:
scope["fastapi_inner_astack"] = request_stack
async with AsyncExitStack() as function_stack:
scope["fastapi_function_astack"] = function_stack
response = await f(request)
await response(scope, receive, send)These two stacks are how Depends(...) callables that yield get their cleanup blocks called after the response is sent. The function_stack closes once the endpoint returns, the inner_stack closes after the response is fully streamed, and the outer middleware_astack (from step 1) closes last.
4. Solve dependencies
solve_dependencies walks the route's Dependant tree:
- Reads the request body once (form/multipart parsing is gated on the route).
- For each parameter, validates with the matching
ModelFieldand accumulates errors. - For each
Depends(...), recursively resolves the sub-dependency, caching by(callable, scopes, computed_scope)whenuse_cache=True. - For
Security(...), propagatesparent_oauth_scopesso a sub-dependency can see what scopes the operation requires. - For special markers (
Request,Response,BackgroundTasks,WebSocket,SecurityScopes,HTTPConnection), populates the field directly.
If any error accumulates, solve_dependencies raises RequestValidationError(errors, body, endpoint_ctx) — the route hands it up the middleware stack, which the default request_validation_exception_handler converts to 422.
5. Endpoint invocation
If the endpoint is async def, it is awaited directly. If it is def, Starlette's run_in_threadpool runs it on the AnyIO threadpool. The route uses Dependant.is_coroutine_callable (a @cached_property) to avoid recomputing this on every request.
If the user wrote Depends(...) with a generator (def gen(): yield) or async generator, the resolver enters its context manager and registers the cleanup on the function or inner exit stack, depending on Dependant.computed_scope.
6. Response serialization
serialize_response (fastapi/routing.py) handles three cases:
| Endpoint return shape | Behaviour |
|---|---|
Already a Response instance |
Returned as-is. response_class and response_model are ignored. |
Plain Python value, no response_model set |
Encoded with jsonable_encoder and wrapped in the route's response_class. |
Plain Python value, response_model set |
Validated against the model field; on success either dumped to JSON bytes via Pydantic's fast path (when response_class is JSON-capable) or encoded with jsonable_encoder. On failure raises ResponseValidationError. |
For response_class=EventSourceResponse, the routing layer takes a different branch — see Server-Sent Events.
7. Send and unwind
After the response streams to the client, the three AsyncExitStacks unwind in LIFO order: function stack → inner stack → middleware stack. Background tasks (see Background tasks) run between the response being constructed and the function stack closing, courtesy of Starlette's BackgroundTasks integration.
WebSocket variant
The same shape applies, with request_response() replaced by websocket_session() (fastapi/routing.py). The exit stacks still wrap the call. There is no serialize_response: WebSocket endpoints communicate via await websocket.send_* calls. Validation errors close the socket with WS_1008_POLICY_VIOLATION (fastapi/exception_handlers.py:websocket_request_validation_exception_handler).
Where to look when this behaves wrong
- Wrong response shape:
serialize_responseand the route'sresponse_model/response_class. - Cleanup runs at the wrong time: scope of the dependency (
Dependant.computed_scope) and which stack it registers on. - 500s from "Response not awaited": a
yield-style dependency caught and dropped an exception. Look for bareexceptorexcept Exceptioninside dependency yields. - Validation errors with no endpoint context: the route's
Dependantwas built withoutendpoint_ctx. See_extract_endpoint_contextinfastapi/routing.py.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.
Previous
Features
Next
Dependency injection