After splitting routers, the same authentication, database, and settings code still repeats across endpoints. Dependency injection encourages us to receive tools from the outside instead of creating them inside every function. FastAPI exposes a compact interface called Depends so you can inject helpers as parameters. Critical logic stays in one place and tests can swap dependencies easily once you understand the flow.
Why bother with Depends?
Without it you end up hand-wiring helpers into every endpoint:
settings = get_settings()
@app.get("/info")
def read_info():
return settings
@app.get("/tasks")
def list_tasks():
session = get_session()
...
Depends centralizes these helpers so you describe requirements (“this endpoint needs settings + a DB session”) and let FastAPI run the plumbing once per request.
Warm-up: Depends in plain language
Dependsmeans “call this helper before the endpoint and pass its return value in.”- Dependency functions usually prepare reusable values such as configuration, DB sessions, or the current user.
- Endpoint functions simply declare “I need this value,” and FastAPI provides it.
Keep these three lines in mind while reading the next sections.
Key terms
- Dependency injection (DI): Receiving required resources from outside the function instead of instantiating them yourself. Prevents repeating authentication or DB boilerplate.
- Depends: FastAPI’s syntax sugar that executes a function and injects the result into the endpoint parameters.
- Request scope: The lifetime of a single HTTP request. Dependencies run once per request and FastAPI caches their results for that request before cleaning up.
- Sub-dependency: A dependency that depends on another dependency.
get_current_userusesget_tokenandget_sessionas sub-dependencies. - Bearer token: A token sent in the
Authorization: Bearer <token>header. We use it while building an authentication dependency. - dependency_overrides: A FastAPI setting that swaps dependencies with fakes during tests or special cases.
Practice card
- Estimated time: 45 minutes
- Prereqs: Part 7 structure, basic knowledge of Python functions and context managers
- Goal: Inject settings/DB/auth dependencies with
Depends, then override them in tests
Minimal example to grasp the shape
from typing import Dict
from fastapi import Depends, FastAPI
app = FastAPI()
def get_settings() -> Dict[str, str]:
# In a real app, pull this from environment variables or a config file
return {"service": "todo", "version": "0.1.0"}
@app.get("/info")
def read_info(settings: Dict[str, str] = Depends(get_settings)):
return settings
Depends(get_settings) runs once per request, caches the result, and injects it anywhere in the call stack that needs settings.
Manage request-scoped resources
Use yield inside a dependency to clean up resources, similar to a context manager. Opening and closing a DB session per request is a classic use case.
from collections.abc import Iterator
from sqlmodel import Session, select
def get_session() -> Iterator[Session]:
session = Session(engine)
try:
yield session
finally:
session.close()
@router.get("/tasks")
def list_tasks(session: Session = Depends(get_session)):
return session.exec(select(Task)).all()
FastAPI always runs the block after yield, so the session closes even when exceptions occur and the connection goes back into the pool. Need async database access? Swap Session for AsyncSession and wrap the body with async with AsyncSession(engine) as session: before yielding.
(Optional) Chain dependencies for reuse
Dependencies can depend on other dependencies—FastAPI calls them sub-dependencies. Parsing a token and then loading the current user is a classic flow. Skip this section if you are not ready for token basics yet.
from fastapi import Header
def get_token(auth_header: str | None = Header(default=None, alias="Authorization")) -> str:
if not auth_header:
raise HTTPException(status_code=401, detail="Missing authorization header")
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != "bearer":
raise HTTPException(status_code=401, detail="Invalid authorization header")
return parts[1]
def get_current_user(
token: str = Depends(get_token),
session: Session = Depends(get_session),
):
user = session.exec(select(User).where(User.token == token)).first()
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
@router.get("/me")
def read_me(user: User = Depends(get_current_user)):
return user
FastAPI first extracts the header, then opens a DB session, then resolves the current user. No more repeated auth or session logic in every router.
(Optional) Dependency chain at a glance
Request arrives
↓
get_token() reads Authorization header
↓
get_session() opens a DB connection
↓
get_current_user() combines both results
↓
read_me() receives the resolved user
↓
Response returns, then FastAPI closes the session
Treat each dependency like a node in a flow chart: swap one node (for example, a fake user) and the rest of the pipeline keeps working.
(Optional) Testing with dependency overrides
During tests, replace dependencies with fakes via app.dependency_overrides[...]. Make sure you clean up overrides after each test so they do not leak.
from fastapi.testclient import TestClient
client = TestClient(app)
def fake_user() -> User:
return User(id=0, email="[email protected]")
def test_read_me_returns_fake_user():
app.dependency_overrides[get_current_user] = fake_user
response = client.get("/me")
app.dependency_overrides.clear()
assert response.status_code == 200
assert response.json()["email"] == "[email protected]"
For pytest, wrap the override cleanup inside a fixture so each test starts fresh.
Recap
Dependsis syntactic sugar, but it couples nicely with typing, docs, and lifecycle management.- The
yieldpattern safely releases request-scoped resources. dependency_overridesmakes it trivial to stub dependencies in tests.
Practice
- Follow along: Build
get_settingsandget_session, then inject them withDepends. - Extend: Chain token parsing and user lookup to complete a
/meendpoint. - Debug: Override dependencies in tests to confirm responses change when you inject fake users/sessions.
- Done when: Both the running server and tests receive the expected objects through the dependency chain.
Wrap-up
Once you internalize Depends, you stop rewriting auth, config, and DB boilerplate. Split common logic into functions, inject them, and you get tests and extensions nearly for free. Next we will connect these dependencies to a real database using SQLModel and sessions.
💬 댓글
이 글에 대한 의견을 남겨주세요