Dependency injection with Depends

FastAPI · Practical · 14 min

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(...) and FastAPI(...) accept dependencies= too, applying them to every route.
  • def and async def dependencies 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)   # 401

Remember

  • 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)).

Check yourself

  1. Two dependencies in one request both depend on get_settings. How many times does get_settings run?
  2. You need a dependency that only verifies an API key and returns nothing useful. What's the cleanest declaration?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: fastapi.tiangolo.com/tutorial/dependencies/