Skip to main content

Dependency Injection

FastAPI's Depends separates common logic (authentication, DB connections, pagination) into dependency functions that can be easily swapped out during testing.


Basic Depends​

from fastapi import FastAPI, Depends, HTTPException
from typing import Annotated

app = FastAPI()


# Dependency function: handle common query parameters
def common_parameters(
skip: int = 0,
limit: int = 10,
q: str | None = None,
):
return {"skip": skip, "limit": limit, "q": q}


# Concise with type hints
CommonParams = Annotated[dict, Depends(common_parameters)]


@app.get("/items/")
def list_items(commons: CommonParams):
return {"params": commons, "items": []}


@app.get("/users/")
def list_users(commons: CommonParams):
return {"params": commons, "users": []}


# Class-based dependency
class Pagination:
def __init__(self, page: int = 1, size: int = 20):
self.skip = (page - 1) * size
self.limit = size
self.page = page


@app.get("/products/")
def list_products(pagination: Annotated[Pagination, Depends()]):
return {
"page": pagination.page,
"skip": pagination.skip,
"limit": pagination.limit,
}

Nested Dependencies​

from fastapi import FastAPI, Depends, Header, HTTPException
from typing import Annotated

app = FastAPI()

fake_tokens = {"alice-token": "Alice", "bob-token": "Bob"}
fake_users = {"Alice": {"id": 1, "role": "admin"}, "Bob": {"id": 2, "role": "user"}}


# Step 1: extract token
def get_token(x_token: Annotated[str, Header()]) -> str:
if x_token not in fake_tokens:
raise HTTPException(status_code=401, detail="Invalid token")
return x_token


# Step 2: look up user from token (depends on step 1)
def get_current_user(token: Annotated[str, Depends(get_token)]) -> dict:
username = fake_tokens[token]
return fake_users[username]


# Step 3: check admin (depends on step 2)
def require_admin(user: Annotated[dict, Depends(get_current_user)]) -> dict:
if user["role"] != "admin":
raise HTTPException(status_code=403, detail="Admin only")
return user


CurrentUser = Annotated[dict, Depends(get_current_user)]
AdminUser = Annotated[dict, Depends(require_admin)]


@app.get("/me")
def read_me(user: CurrentUser):
return user


@app.get("/admin/dashboard")
def admin_dashboard(admin: AdminUser):
return {"message": "Welcome Admin", "user": admin}

Resource Management — yield Dependencies​

from fastapi import FastAPI, Depends
from typing import Annotated, Generator

app = FastAPI()


class DBSession:
def __init__(self, db_url: str):
self.url = db_url
self.queries: list[str] = []
print(f"[DB] Connected: {db_url}")

def execute(self, query: str) -> list:
self.queries.append(query)
return [{"id": 1, "data": query}]

def close(self):
print(f"[DB] Disconnected ({len(self.queries)} queries executed)")


DATABASE_URL = "postgresql://localhost/mydb"


# yield dependency: setup before yield, teardown after
def get_db() -> Generator[DBSession, None, None]:
db = DBSession(DATABASE_URL)
try:
yield db
finally:
db.close() # always runs (even on exception)


DB = Annotated[DBSession, Depends(get_db)]


@app.get("/users/")
def list_users(db: DB):
return db.execute("SELECT * FROM users")


@app.get("/users/{user_id}")
def get_user(user_id: int, db: DB):
return db.execute(f"SELECT * FROM users WHERE id={user_id}")

Global Dependencies​

from fastapi import FastAPI, Depends, Header, HTTPException
from typing import Annotated

app = FastAPI()


async def verify_api_key(x_api_key: Annotated[str, Header()]):
if x_api_key != "secret-key":
raise HTTPException(status_code=403, detail="Invalid API Key")


# Apply dependency to all routes in a router
from fastapi import APIRouter

router = APIRouter(
prefix="/api",
dependencies=[Depends(verify_api_key)],
)


@router.get("/data")
def get_data():
return {"data": "protected"}


@router.get("/more-data")
def get_more_data():
return {"data": "also protected"}


# App-wide global dependency
app_with_global = FastAPI(
dependencies=[Depends(verify_api_key)]
)

Dependency Overrides (Testing)​

from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
from typing import Annotated

app = FastAPI()


# Real dependency
def get_db():
return {"type": "production_db"}


DB = Annotated[dict, Depends(get_db)]


@app.get("/items/")
def list_items(db: DB):
return {"db_type": db["type"]}


# Replace dependency in tests
def get_test_db():
return {"type": "test_db"}


def test_list_items():
app.dependency_overrides[get_db] = get_test_db # swap

client = TestClient(app)
response = client.get("/items/")

assert response.status_code == 200
assert response.json()["db_type"] == "test_db"

app.dependency_overrides.clear() # restore

Summary​

ConceptDescription
Depends(fn)Inject a dependency function
Nested dependenciesDependencies that use other dependencies
yield dependencysetup/teardown pattern
Global dependenciesrouter(dependencies=[...])
dependency_overridesSwap dependencies in tests
AnnotatedEmbed dependency in type hint

Dependency injection makes it easy to reuse authentication, DB sessions, and common parameters while keeping tests simple.