FastAPI Under the Hood: ASGI, Dependency Injection, and the Lifespan Pattern
A Python backend that handles concurrent I/O without spawning extra workers changes the cost calculus for API services. Understanding the `yield`-based dependency lifecycle and the static-before-dynamic routing rule prevents two of the most common production bugs in FastAPI applications.
FastAPI's speed comes from ASGI, which lets a single worker juggle multiple requests by yielding the CPU during I/O waits, unlike WSGI's one-request-per-worker model. The framework itself is a thin layer over Starlette (routing, middleware) and Pydantic (validation, serialization), adding syntactic sugar that cuts boilerplate in half compared to Flask.
The dependency injection system built around `Depends` uses generator functions with `yield` to hand resources like database sessions into route handlers and then reliably clean them up in a `finally` block, even when exceptions occur. Dependencies can nest and are cached per request, so a single session is reused across multiple parameters without repeated creation.
Route organization relies on `APIRouter` for modularity, but registration order matters: static paths must precede dynamic ones or `/users/me` gets swallowed by `/users/{user_id}`. The new `lifespan` context manager centralizes startup and shutdown logic in one async generator, replacing the now-deprecated `on_event` decorators and making shared state easier to pass around.
FastAPI's design makes the framework feel almost invisible: the heavy lifting is delegated to Starlette and Pydantic, and FastAPI itself contributes mostly ergonomics. That explains both its speed and its shallow learning curve for developers who already know type hints.
The `yield`-based dependency pattern is a clever abuse of Python generators. It turns a language feature designed for lazy iteration into a deterministic resource-management primitive, giving FastAPI something resembling middleware but scoped to individual route parameters.
Routing order as a footgun is a direct consequence of FastAPI's first-match-wins strategy. Most frameworks warn about this; FastAPI silently converts `me` to an integer and returns a 422, which is correct behavior but a confusing debugging experience for newcomers.
Offloading synchronous `def` routes to a thread pool is a pragmatic escape hatch, but it hides a design tension: the framework advertises async performance while most SQLAlchemy users still write blocking code. The thread-pool workaround keeps the event loop clear but adds scheduling overhead.