Understanding Depends
Introduction
Depends is FastAPI's dependency injection primitive. You point a route parameter at a callable, and FastAPI runs that callable before the route function and passes its return value as the argument. It is the foundation of how FastAPI shares logic, manages resources, and wires authentication.
Why This Matters
Most non-trivial routes need supporting values: a database session, a logged-in user, a parsed configuration object. Without Depends, you would either build these inside every route or pass them through manually. Depends keeps the wiring out of your route bodies and centralizes shared behavior in one callable per concern.
A Minimal Dependency
A dependency is just a function. FastAPI calls it for you and injects its return value:
from fastapi import FastAPI, Depends
app = FastAPI()
def common_params(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
@app.get("/items")
def list_items(params: dict = Depends(common_params)):
return paramsA request to /items?skip=20&limit=5 runs common_params(skip=20, limit=5), captures the dict, and passes it to list_items as params.
How FastAPI Sees the Dependency
When FastAPI inspects the dependency function, it treats its parameters exactly like a route's parameters:
skipandlimitare simple types with defaults, so they become query parameters in the OpenAPI schema.- A Pydantic model parameter would become a request body.
- A
PathorQueryannotation would behave the same as on a route.
The injected dict is just the return value of common_params.
Sharing One Dependency Across Routes
The point of dependencies is reuse. Once defined, the same callable can serve many routes:
@app.get("/items")
def list_items(params: dict = Depends(common_params)):
return params
@app.get("/users")
def list_users(params: dict = Depends(common_params)):
return params
@app.get("/orders")
def list_orders(params: dict = Depends(common_params)):
return paramsEach route gets parsed pagination parameters without restating them.
Async Dependencies
Dependencies can be async if they need to await something:
import asyncio
async def slow_lookup(item_id: int) -> str:
await asyncio.sleep(0.1)
return f"item-{item_id}"
@app.get("/items/{item_id}")
async def get_item(name: str = Depends(slow_lookup)):
return {"name": name}FastAPI handles sync and async dependencies transparently.
Dependencies That Depend on Other Dependencies
Dependencies can themselves take Depends parameters:
def get_token(token: str | None = None) -> str:
if token is None:
return "anonymous"
return token
def get_user(token: str = Depends(get_token)) -> str:
return f"user-from-{token}"
@app.get("/me")
def me(user: str = Depends(get_user)):
return {"user": user}FastAPI resolves the chain top-down: get_token runs first, its result feeds into get_user, and get_user's result reaches the route.
Caching Within a Request
By default, if the same dependency is used multiple times in the same request, FastAPI calls it once and reuses the result. This is important for things like database sessions, where you want all parts of a request to share the same connection:
def get_settings():
return {"theme": "dark"}
def get_theme(settings: dict = Depends(get_settings)) -> str:
return settings["theme"]
@app.get("/")
def root(
settings: dict = Depends(get_settings),
theme: str = Depends(get_theme),
):
return {"settings": settings, "theme": theme}get_settings runs once even though it appears twice in the dependency graph.
To disable caching, pass use_cache=False:
@app.get("/")
def root(value: int = Depends(make_value, use_cache=False)):
return {"value": value}Dependencies Without a Return Value
A dependency can perform work without injecting anything - it might validate a header or log a metric. Declare it in the route's dependencies list:
from fastapi import Header, HTTPException
def verify_api_key(x_api_key: str = Header()):
if x_api_key != "secret":
raise HTTPException(status_code=401, detail="Invalid API key")
@app.get("/items", dependencies=[Depends(verify_api_key)])
def list_items():
return [{"name": "Pen"}]The dependency runs, may raise, but does not contribute a parameter.
Common Mistakes
Forgetting to call Depends
def f(x: dict = common_params) is just a default value, not a dependency. Wrap the callable in Depends(common_params).
Doing heavy work inside the route
If the same logic appears at the top of every route, it belongs in a dependency. The route body should describe what the endpoint does, not how the inputs are prepared.
Importing the wrong Depends
Depends lives in fastapi, not pydantic or starlette. Always from fastapi import Depends.
Summary
Depends runs a callable before your route and passes its return value as a parameter. Dependencies can take their own dependencies, are cached within a request by default, and can be plain side-effect-only checks. Mastering this one tool unlocks most of FastAPI's structuring power.
How is this guide?
Last updated on
