Skip to content

Dependency Injection (Depends)

Dependency Injection (DI) lets you declare what your endpoint needs (database, current user, config) — FastAPI automatically provides it. Shared logic becomes reusable, testable, and decoupled.

The simplest possible example

from fastapi import FastAPI, Depends

app = FastAPI()

def common_pagination(page: int = 1, size: int = 10):
    return {"page": page, "size": size}

@app.get("/users")
def list_users(pagination = Depends(common_pagination)):
    return {"pagination": pagination, "users": [...]}

@app.get("/products")
def list_products(pagination = Depends(common_pagination)):
    return {"pagination": pagination, "products": [...]}

Now both endpoints get ?page=2&size=20 for free, with the same validation logic — no duplication.

How it executes

When /users?page=2 comes in:

  1. FastAPI sees pagination = Depends(common_pagination).
  2. It calls common_pagination(page=2, size=10) with parsed query params.
  3. It passes the return value into your endpoint as pagination.
  4. Your function runs.

That's it. Dependencies are just functions FastAPI calls before your endpoint.

Why use Depends at all?

Concern Without DI With DI
Reuse the same logic in 10 endpoints Copy-paste 10 times Write once, Depends(...) everywhere
Test an endpoint Hard — DB connection baked in Easy — swap dependency in tests
"Current user" check Repeat in every endpoint One get_current_user dependency
DB session per request Manual setup + teardown One dependency handles open/close

DB session — the classic example

# db.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine("sqlite:///app.db", connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db                     # yield (not return) — see below
    finally:
        db.close()
# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from db import get_db

app = FastAPI()

@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
    return db.query(User).filter(User.id == user_id).first()

yield + finally — guarantees the DB session closes even if your endpoint raises an exception. That's the dependency-with-teardown pattern.

Authentication — get_current_user

from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

def get_current_user(authorization: str = Header(...)):
    if not authorization.startswith("Bearer "):
        raise HTTPException(401, "missing bearer token")
    token = authorization.split()[1]
    user = decode_token(token)           # imagine this exists
    if not user:
        raise HTTPException(401, "invalid token")
    return user

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

@app.get("/me/orders")
def my_orders(user = Depends(get_current_user)):
    return {"user": user["name"], "orders": [...]}

Add Depends(get_current_user) to any endpoint to require login.

Dependency that has its own dependencies

Dependencies can depend on other dependencies — FastAPI resolves the chain.

def get_db():
    ...

def get_user_repository(db: Session = Depends(get_db)):
    return UserRepository(db)

def get_current_user(
    repo: UserRepository = Depends(get_user_repository),
    token: str = Header(..., alias="authorization"),
):
    user = repo.find_by_token(token)
    if not user:
        raise HTTPException(401)
    return user

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

When /profile is called: - get_db runs → opens DB session. - get_user_repository runs with that session. - get_current_user runs with that repo + the token. - Your endpoint runs with the resulting user. - After the endpoint finishes, get_db's finally block closes the session.

All wired up automatically.

Class-based dependencies

For dependencies with multiple parameters, a class is often cleaner:

class Paginator:
    def __init__(self, page: int = 1, size: int = 10, sort: str = "id"):
        self.page = page
        self.size = size
        self.sort = sort

    @property
    def offset(self) -> int:
        return (self.page - 1) * self.size

@app.get("/items")
def list_items(p: Paginator = Depends()):
    return {"page": p.page, "offset": p.offset, "sort": p.sort}

You can omit Paginator inside Depends() — FastAPI infers it from the type hint.

Apply a dependency to many endpoints — dependencies= parameter

If a dependency runs just for side-effects (auth check, rate limit) and you don't need its return value, attach it to the path operation or the entire router:

from fastapi import FastAPI, Depends, APIRouter

def verify_token(authorization: str = Header(...)):
    if not authorization.startswith("Bearer "):
        raise HTTPException(401)

# Per-endpoint
@app.get("/admin", dependencies=[Depends(verify_token)])
def admin_page():
    return {"message": "admin only"}

# Per-router
router = APIRouter(dependencies=[Depends(verify_token)])

@router.get("/secret")
def secret():
    return {"hello": "world"}

App-level dependencies

For something that should run on every request:

app = FastAPI(dependencies=[Depends(verify_token)])

Now every endpoint requires the bearer token. Useful for fully-authenticated APIs.

Dependency overrides — for tests

Tests can replace any dependency:

# test_app.py
from fastapi.testclient import TestClient
from main import app, get_db

def fake_db():
    yield InMemoryDB()

app.dependency_overrides[get_db] = fake_db

client = TestClient(app)
r = client.get("/users/1")
assert r.status_code == 200

Run integration tests without a real database. Powerful.

Dependency with yield — startup, teardown, error handling

def get_db():
    db = SessionLocal()
    try:
        yield db
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

Useful pattern: open resource → yield → cleanup, rollback on error.

When NOT to use Depends

  • For pure data classes — use Pydantic models.
  • For a single endpoint's logic — just write it inline.
  • For configuration that never changes — read from a global.

Putting it together — a realistic mini-app

from fastapi import FastAPI, Depends, HTTPException, Header
from pydantic import BaseModel

app = FastAPI()

# --- "DB" (fake)
USERS = {
    "tok_alice": {"id": 1, "name": "Alice", "role": "admin"},
    "tok_bob":   {"id": 2, "name": "Bob",   "role": "user"},
}

# --- Dependencies
def get_current_user(authorization: str = Header(...)):
    if not authorization.startswith("Bearer "):
        raise HTTPException(401, "missing bearer")
    token = authorization.split()[1]
    user = USERS.get(token)
    if not user:
        raise HTTPException(401, "invalid token")
    return user

def require_admin(user: dict = Depends(get_current_user)):
    if user["role"] != "admin":
        raise HTTPException(403, "admin only")
    return user

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

@app.get("/admin/dashboard")
def admin(user = Depends(require_admin)):
    return {"hello_admin": user["name"]}

Hit:

curl -H "Authorization: Bearer tok_alice" http://127.0.0.1:8000/me
curl -H "Authorization: Bearer tok_alice" http://127.0.0.1:8000/admin/dashboard   # ok
curl -H "Authorization: Bearer tok_bob"   http://127.0.0.1:8000/admin/dashboard   # 403
curl http://127.0.0.1:8000/admin/dashboard                                          # 422 (missing header)

Common pitfalls

  • Forgetting Depends(...)db: Session = get_db (no Depends) makes FastAPI think get_db is a default value, not a dependency. Always wrap in Depends(...).
  • Heavy work in __init__ of class dependencies — runs on every request. Use lazy initialization or caching.
  • Mixing sync and async dependencies — works, but async deps can't be called from a sync def endpoint. Match them.
  • Caching that's too aggressiveDepends(use_cache=True) (the default) caches within a request. If you need fresh each call, use use_cache=False.
  • Circular dependencies — splitting modules helps. If A depends on B and B on A, you have a design problem.

What's next

Practice

What does this print?

Expected: db_session

# FastAPI's Depends() injects a value computed by a dependency function
dependency_name = "db_session"
print(dependency_name)

Use Depends() (not direct function call) so FastAPI can inject and override it

Expected: True

# Wrong: calling the function directly — no DI, no override for tests
def get_db(): return "db"
def handler():
    db = get_db()           # bug: should be db: str = Depends(get_db)
    return db
print(callable(get_db))

Quiz — Quick check

What you remember

Q1. What does Depends(get_db) do?

  • Calls get_db() to compute a value, injects it into the handler — and lets you override it for tests
  • Imports the function
  • A type annotation
  • Validates the function

Why: Dependency injection separates "what" (the handler logic) from "how" (where the DB comes from). For tests, override get_db with a mock — no test database needed.

Q2. Why use yield instead of return in a dependency?

  • Code AFTER yield runs after the request completes — useful for cleanup (closing DB sessions, releasing locks)
  • Faster
  • Required by FastAPI
  • No difference

Why: Yield-based dependencies are like context managers. The yielded value is injected; code after yield is the teardown. Critical for resources that need cleanup.

Q3. Can a dependency depend on another dependency?

  • Yes — dependencies can have their own Depends() params, forming a graph
  • No, only one level
  • Yes but only 2 levels
  • Only with decorators

Why: FastAPI builds a dependency graph and resolves it for each request. Common pattern: get_current_user depends on get_db (to look up the user). Each level is automatically resolved in order.

Common doubts

When should I use a dependency vs a regular function call?

Use Depends when (a) the function needs to be overridable for tests, (b) it needs FastAPI's resolution (e.g., extracting headers/tokens), or © it manages a resource lifecycle (DB session, file handle). For pure utility functions, just call them directly.

How do I override dependencies in tests?

app.dependency_overrides[get_db] = lambda: mock_db. Test client now gets the mock instead of the real DB. Clean up with app.dependency_overrides.clear() after the test.

Are dependencies called once per request or once per app start?

Once per request. For singleton dependencies (e.g., a model that's expensive to load), construct it once at startup and inject it from a global — or use lifespan events to manage app-level state.

Middleware & CORS