Dependencies are FastAPI's main tool for reuse and for test seams, and they keep endpoints thin.
A dependency is any callable whose result your endpoint needs. Declare it with Depends: page: Annotated[dict, Depends(pagination)]. For each request FastAPI calls pagination first, supplying whatever it declares (query parameters, headers, other dependencies), and passes the return value in. The endpoint never calls it and doesn't care where the values came from.
Dependencies use the full parameter system: their inputs are validated, they appear in the docs, and they can raise HTTPException to stop the request. That's exactly how authentication checks are built.
The idiomatic pattern is a reusable type alias:
CurrentUser = Annotated[User, Depends(get_current_user)]
def read_me(user: CurrentUser): ...Properties worth knowing:
- Sub-dependencies: dependencies can depend on other dependencies, and FastAPI resolves the whole graph.
- Per-request caching: if several dependencies need the same one, it runs once per request and the value is shared.
Depends(fn, use_cache=False)opts out. - Classes work because they're callable:
Depends(Pagination)builds an instance from query parameters. - Side-effect only:
dependencies=[Depends(verify_key)]on a decorator runs a check without injecting anything.APIRouter(...)andFastAPI(...)acceptdependencies=too, applying them to every route. defandasync defdependencies mix freely.
Dependencies are also your testing seam: app.dependency_overrides[get_db] = fake_db swaps an implementation for tests without touching endpoint code.
Example
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException, Query
from fastapi.testclient import TestClient
def pagination(skip: Annotated[int, Query(ge=0)] = 0,
limit: Annotated[int, Query(le=100)] = 20) -> dict:
return {"skip": skip, "limit": limit}
def api_key(x_api_key: Annotated[str, Header()]) -> None:
if x_api_key != "secret":
raise HTTPException(status_code=401, detail="Bad API key")
Page = Annotated[dict, Depends(pagination)]
app = FastAPI(dependencies=[Depends(api_key)]) # runs for every route
@app.get("/items")
def list_items(page: Page):
return {"page": page}
c = TestClient(app)
print(c.get("/items?limit=5", headers={"X-API-Key": "secret"}).json())
# {'page': {'skip': 0, 'limit': 5}}
print(c.get("/items").status_code) # 422: header missing
print(c.get("/items", headers={"X-API-Key": "nope"}).status_code) # 401Remember
- Annotated[T, Depends(fn)] runs fn per request and injects its result
- Dependencies take parameters and other dependencies, forming a graph
- A dependency used several times in one request runs once (cached)
- dependencies=[...] on routes, routers or the app runs checks without injecting values
Watch out for
- Calling the dependency yourself (Depends(get_db())) instead of passing the function (Depends(get_db)).