Factory.ai

Open-Source Wikis

/

FastAPI

/

Features

/

Request lifecycle

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, …)
    end

Phases in detail

1. Middleware stack

FastAPI.__init__ builds the Starlette middleware stack in this order (outermost first):

  1. ServerErrorMiddleware (Starlette) — catches uncaught exceptions and produces a 500.
  2. User middleware added through app.add_middleware(...)CORSMiddleware, GZipMiddleware, custom auth, etc.
  3. ExceptionMiddleware (Starlette) — translates HTTPException (and registered custom exceptions) into responses.
  4. AsyncExitStackMiddleware (FastAPI) — opens scope["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 ModelField and accumulates errors.
  • For each Depends(...), recursively resolves the sub-dependency, caching by (callable, scopes, computed_scope) when use_cache=True.
  • For Security(...), propagates parent_oauth_scopes so 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_response and the route's response_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 bare except or except Exception inside dependency yields.
  • Validation errors with no endpoint context: the route's Dependant was built without endpoint_ctx. See _extract_endpoint_context in fastapi/routing.py.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Request lifecycle – FastAPI wiki | Factory