Factory.ai

Open-Source Wikis

/

FastAPI

/

Systems

/

Routing

fastapi/fastapi

Routing

Purpose

fastapi/routing.py (4,956 lines) is where path-operation decorators turn into ASGI handlers. It defines APIRouter, the route classes (APIRoute for HTTP, APIWebSocketRoute for WebSockets), the request/response wrappers that integrate with the dependency system, and the response serializer.

Directory layout

fastapi/
├── routing.py                 # APIRouter, APIRoute, APIWebSocketRoute, helpers
├── dependencies/
│   ├── models.py              # Dependant dataclass
│   └── utils.py               # solve_dependencies, get_dependant
├── middleware/
│   └── asyncexitstack.py      # AsyncExitStackMiddleware
└── exception_handlers.py      # default handlers

Key abstractions

Symbol Description
APIRouter A starlette.routing.Router subclass that adds add_api_route, add_api_websocket_route, include_router, dependency-with-prefix support, and helper decorators (.get, .post, .put, .patch, .delete, .options, .head, .trace, .api_route, .websocket).
APIRoute Subclass of starlette.routing.Route. Holds the endpoint, the response model, the response class, the status code, dependencies, OpenAPI metadata, and a compiled Dependant tree. Builds an ASGI app from the endpoint via request_response.
APIWebSocketRoute Same as APIRoute but for WebSockets. Uses the websocket_session wrapper.
request_response(func) Wraps a function into an ASGI app. Sets up the two request-scoped AsyncExitStacks on the scope, invokes the user's function, and runs the response. Raises a verbose FastAPIError if a yield-style dependency swallowed an exception.
serialize_response(...) Validates a return value against the route's response_model (or its return-type annotation), then either returns the validated value or — when response_class is JSON-capable and a model is set — dumps it directly to JSON bytes for speed.
_endpoint_context_cache Module-level cache keyed on id(func); populated by _extract_endpoint_context to cache file/line/name lookups. The cache is read in the validation-error paths so error messages can name the offending endpoint.
_DefaultLifespan Vendored copy of the Starlette class that runs on_startup and on_shutdown. Starlette removed it (see PR ref in the comment); FastAPI keeps it for backward compatibility.

How it works

graph TD
    Decorator["@router.get(path)"] --> AddRoute["APIRouter.add_api_route"]
    AddRoute --> Construct["APIRoute(path, endpoint, ...)"]
    Construct --> BuildDep["get_dependant(call=endpoint, ...)"]
    BuildDep --> Dependant["Dependant tree"]
    Construct --> Wrap["request_response(get_route_handler())"]
    Wrap --> ASGIApp["ASGI app stored on the route"]
    ASGIApp -->|on request| Solve["solve_dependencies(request, dependant, ...)"]
    Solve -->|values, errors| Endpoint["endpoint(**values)"]
    Endpoint --> Serialize["serialize_response(...)"]
    Serialize --> Response["Response (JSON / stream / bytes)"]

APIRoute.get_route_handler() returns the inner async function that:

  1. Reads the request body once (handing form/multipart data to python-multipart when relevant).
  2. Walks the route's Dependant via solve_dependencies.
  3. Calls the endpoint (sync endpoints go through run_in_threadpool).
  4. If the endpoint returned a Response instance, sends it as-is. Otherwise calls serialize_response and wraps the result in the route's response_class.

include_router(other, prefix="/api/v1", dependencies=[...], tags=[...], responses={...}) merges another router. It walks the sub-router's routes, prepends the prefix, concatenates dependencies and tags, deep-merges the responses dict (fastapi/utils.py:deep_dict_update), and registers each child route on the parent.

Routing for Server-Sent Events

For response_class=EventSourceResponse (fastapi/sse.py), the path operation function is expected to be an async generator yielding ServerSentEvent objects, plain dicts, Pydantic models, or None for keep-alive ticks. The routing layer detects the SSE response class and switches into the SSE encoding path (format_sse_event from fastapi/sse.py), with a 15-second keep-alive (_PING_INTERVAL) that emits : ping comments to keep proxies from idling out. Any HTTP method works, including POST, which makes the implementation MCP-compatible. See Server-Sent Events for the full picture.

Integration points

  • Up to FastAPIFastAPI.router is an APIRouter. Decorators on FastAPI forward here.
  • Down to dependencies/get_dependant is called once at registration; solve_dependencies runs on every request.
  • Down to _compatserialize_response uses ModelField.serialize from fastapi/_compat/v2.py for fast JSON dumps.
  • Down to openapi/utils.py — the schema generator inspects every APIRoute to build the OpenAPI document.

Entry points for modification

  • New decorator (e.g. a verb): add a thin method to APIRouter mirroring .get, then mirror it on FastAPI.
  • New per-route option: add it to APIRoute.__init__, plumb it through add_api_route, add_api_websocket_route (where relevant), include_router, and the FastAPI decorators.
  • Changing serialization defaults: modify serialize_response. Keep the Pydantic-fast-path branch; tests under tests/test_dump_json_fast_path.py cover it.
  • Diagnostics: extend EndpointContext (fastapi/exceptions.py) or _extract_endpoint_context to add new metadata; the validation errors will surface it automatically.

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

Routing – FastAPI wiki | Factory