FastAPI’s DI is a quiet superpower. Used well, it scales to dozens of endpoints without becoming the boilerplate that other Python frameworks become. This post is the patterns from real codebases.
Typed dep aliases
from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db() -> AsyncIterator[AsyncSession]:
async with SessionLocal() as session:
yield session
DBSession = Annotated[AsyncSession, Depends(get_db)]
# In handlers:
@app.get("/users/{id}")
async def get_user(id: int, db: DBSession) -> User:
...
DBSession is a type alias. Every endpoint uses db: DBSession instead of db: AsyncSession = Depends(get_db). Cleaner.
For SQLAlchemy 2.0 patterns .
Authenticated user
async def current_user(
request: Request,
db: DBSession,
) -> User:
token = request.headers.get("authorization", "").removeprefix("Bearer ")
user = await verify_token(db, token)
if not user:
raise HTTPException(401, "unauthenticated")
return user
CurrentUser = Annotated[User, Depends(current_user)]
@app.get("/me")
async def me(user: CurrentUser) -> User:
return user
Standard pattern. Failure raises HTTPException; user is always set.
For Authentication .
Permissions / role checks
class RequiresRole:
def __init__(self, role: str):
self.role = role
async def __call__(self, user: CurrentUser):
if self.role not in user.roles:
raise HTTPException(403, f"requires {self.role}")
return user
AdminUser = Annotated[User, Depends(RequiresRole("admin"))]
@app.delete("/users/{id}")
async def delete_user(id: int, admin: AdminUser, db: DBSession):
...
Class-based dep takes init args. The instance is callable. FastAPI calls it as a dep. Clean composition.
Request-scoped vs app-scoped
# App-scoped: created once at startup
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.http = httpx.AsyncClient()
app.state.cache = redis.Redis(...)
yield
await app.state.http.aclose()
app.state.cache.close()
# Request-scoped: created per-request via Depends
async def get_db():
async with SessionLocal() as s:
yield s
App-scoped: shared resources (HTTP client, cache, ML model). Created once. Request-scoped: per-request state (DB session, current user). Cleaned up at end.
Sub-dependencies
Deps depend on other deps:
async def get_org(user: CurrentUser, db: DBSession) -> Organization:
return await db.get(Organization, user.org_id)
CurrentOrg = Annotated[Organization, Depends(get_org)]
@app.get("/team")
async def team(org: CurrentOrg, db: DBSession):
...
get_org depends on current_user and get_db. FastAPI resolves the graph; calls each in order; caches per request.
Caching within a request
async def expensive_computation(user: CurrentUser):
return await some_expensive_call(user.id)
@app.get("/a")
async def a(result: Annotated[Result, Depends(expensive_computation)]):
...
@app.get("/b")
async def b(
result1: Annotated[Result, Depends(expensive_computation)],
result2: Annotated[Result, Depends(expensive_computation)], # same call
):
# result1 == result2; expensive_computation called once
...
Within a single request, the same dep is called once. Across requests, called per-request.
For across-request caching, use Redis externally.
Background work
from fastapi import BackgroundTasks
@app.post("/signup")
async def signup(payload: SignUp, bg: BackgroundTasks):
user = await create_user(payload)
bg.add_task(send_welcome_email, user.id)
return user
BackgroundTasks runs after the response returns. Fine for fire-and-forget < 100ms work. For heavier or retry-able work, use proper background jobs
.
Testing with overrides
async def get_test_db():
yield mock_db
app.dependency_overrides[get_db] = get_test_db
In tests, swap any dep for a stub. The override is per-app. See Testing FastAPI Apps .
What I’d ship today
Pattern for new FastAPI app:
Annotatedaliases for every common dep.- Lifespan for app-scoped resources.
- Request-scoped DB session.
CurrentUser+ role-based deps.- Sub-deps for derived resources (org from user).
- Override-friendly for tests.
For the broader app skeleton see FastAPI + Pydantic v2 + SQLAlchemy 2.0 .
Read this next
- FastAPI + Pydantic v2 + SQLAlchemy 2.0 Production Patterns
- SQLAlchemy 2.0 Deep Patterns
- Testing FastAPI Apps in 2026
- Pydantic v2 Deep Dive
If you want my FastAPI app template with all these wired up, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .