Dependency injection with Depends()
The problem: repeated logic across routes
Several routes often need the exact same piece of setup or validation — pagination parameters, a database connection, a shared check — and writing it out in every route function duplicates it:
from fastapi import Query
@app.get("/agents")
def list_agents(skip: int = Query(default=0, ge=0), limit: int = Query(default=10, gt=0, le=100)):
return {"skip": skip, "limit": limit}
@app.get("/tools")
def list_tools(skip: int = Query(default=0, ge=0), limit: int = Query(default=10, gt=0, le=100)):
return {"skip": skip, "limit": limit}Identical skip/limit definitions, copied into every route that
needs pagination — a maintenance problem the moment the constraints
need to change (raising the max limit, say) in more than one place.
A dependency that returns a real, meaningful object
Pagination returning a plain dict is a simple case — dependencies commonly stand in for something more substantial, like a shared resource:
def get_agent_registry():
return agents_db # a shared dict, database session, or similar resource
@app.get("/agents/{agent_id}")
def get_agent(agent_id: int, registry: dict = Depends(get_agent_registry)):
return registry.get(agent_id, {})click Run to see this pane's outputget_agent_registry here is trivial, but the pattern generalizes
directly to something like a real database session — a dependency
function that opens a connection, and every route needing database
access declares Depends(get_db_session) rather than opening its own
connection by hand.
A dependency used only for its side effect
A dependency doesn’t need its return value to matter — sometimes the point is purely the validation or side effect it performs, raising an HTTPException before the route even runs if some shared condition fails:
from fastapi import Header, HTTPException
def verify_request_id(x_request_id: str = Header(default=None)):
if x_request_id is None:
raise HTTPException(status_code=400, detail="X-Request-Id header is required")
@app.get("/agents", dependencies=[Depends(verify_request_id)])
def list_agents():
return {"agents": []}click Run to see this pane's outputdependencies=[Depends(verify_request_id)] on the route decorator
itself (rather than as a function parameter) runs verify_request_id
before list_agents, without needing its result injected anywhere —
its only job is to reject the request early if the header’s missing,
which it does exactly like any other raise you’ve written.
What problem does Depends() solve, compared to repeating the same parameter definitions across multiple routes?