Depends()

intermediate fastapi dependency-injection

Depends() is the single feature that makes FastAPI feel different from Flask or Express. We declare what our handler needs — a DB session, a current user, parsed pagination params — and FastAPI builds those things and injects them. We never call them ourselves.

In simple language — instead of doing user = get_current_user(token) at the top of every handler, we put user: User = Depends(get_current_user) in the signature and FastAPI does it for us, every request.

The core idea

A dependency is just a function. Its parameters can be other request data (Header, Query, Body) or other dependencies. Whatever it returns gets passed into our handler.

Per-request flow
1. Request arrives — FastAPI reads handler signature
2. Sees Depends(get_db) — calls get_db() first
3. Passes the result as the db parameter
4. Handler runs with everything wired up

A first example — pagination params

We want ?skip=0&limit=10 on a bunch of list endpoints. Without DI, we copy-paste the same query params everywhere.

from fastapi import FastAPI, Depends

app = FastAPI()

def pagination(skip: int = 0, limit: int = 10) -> dict:
    return {"skip": skip, "limit": min(limit, 100)}

@app.get("/users")
def list_users(page: dict = Depends(pagination)):
    return {"params": page}

@app.get("/products")
def list_products(page: dict = Depends(pagination)):
    return {"params": page}

Both endpoints now accept skip and limit, the cap on limit is in one place, and Swagger picks them up automatically.

A realistic example — current user from JWT

This is the pattern everyone uses for auth.

from fastapi import Header, HTTPException, Depends

def get_current_user(authorization: str = Header(...)) -> dict:
    if not authorization.startswith("Bearer "):
        raise HTTPException(401, "missing bearer token")
    token = authorization.removeprefix("Bearer ")
    user = decode_jwt(token)  # imagine this exists
    if not user:
        raise HTTPException(401, "invalid token")
    return user

@app.get("/me")
def me(user: dict = Depends(get_current_user)):
    return user

@app.post("/posts")
def create_post(payload: PostIn, user: dict = Depends(get_current_user)):
    return {"author": user["id"], "title": payload.title}

The Depends(get_current_user) line on every protected endpoint is literally the auth check. If the dependency raises HTTPException, the handler never runs.

The two ways to write it

These are equivalent — pick whichever your team prefers:

# Style 1: in default value
def handler(user: User = Depends(get_current_user)): ...

# Style 2: with Annotated (FastAPI 0.95+, recommended)
from typing import Annotated
CurrentUser = Annotated[User, Depends(get_current_user)]

def handler(user: CurrentUser): ...

The Annotated form lets us alias common dependencies and reuse them. Much cleaner once the codebase grows.

Per-request caching — the magic

If the same dependency appears multiple times in one request (in the handler AND in another dependency), FastAPI calls it once and reuses the result.

Same dependency, same request → called once
handler
Depends(get_db)
get_db()
called 1x
get_current_user
Depends(get_db)
Both receive the same db session

This is huge — we can use the same get_db dependency in three places and still get one connection per request, not three.

To disable caching for a specific dependency, use Depends(get_db, use_cache=False).

Path / router-level dependencies

When a dependency exists only for its side effects (rate limit check, audit log), we don’t want its return value cluttering the handler signature. Attach it at the route or router level:

def verify_api_key(x_api_key: str = Header(...)):
    if x_api_key != "secret":
        raise HTTPException(403)

# Single route
@app.get("/admin/stats", dependencies=[Depends(verify_api_key)])
def stats():
    return {...}

# Entire router
from fastapi import APIRouter
router = APIRouter(dependencies=[Depends(verify_api_key)])

Why this beats decorators

Frameworks like Flask solve auth with @login_required decorators. The problem — decorators are opaque, can’t be composed cleanly, can’t be type-hinted, and don’t show up in OpenAPI docs.

Depends() is a regular value in the signature. Type checkers see it, IDE autocompletes it, the result is statically typed, and the params it depends on (Header, Query) appear in Swagger UI automatically. We compose dependencies like we compose any other function.

The takeaway — Depends() flips control. Instead of imperatively pulling data inside the handler, we declaratively list what the handler needs and let FastAPI assemble it. Caching, error propagation, and docs all come along for free.