Using FastAPI Dependency Injection for Safe Resource Management
Learn how FastAPI's Depends and yield patterns eliminate duplicated setup code and guarantee cleanup of database sessions, auth tokens, and other shared resources.
24 Jul 2026, 22:46 UTC

The Problem: Duplicated Setup and Risk of Leaks
Every route that needs a database session or an external client often repeats the same creation and close logic. This duplication not only bloats the code but also makes it easy to forget a cleanup step, leaving connections open when an exception occurs.
How FastAPI's Depends Works
FastAPI treats any callable as a dependency. When a route parameter is annotated with Depends(callable), the framework runs that callable before the handler and injects its return value. If the callable uses yield, everything before the yield runs before the request, and everything after runs after the response, acting as a context manager.
Worked Example: Request‑Scoped SQLAlchemy Session
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/items/{item_id}")
def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.execute(
"SELECT * FROM items WHERE id = :id", {"id": item_id}
).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": item}
Hierarchical Dependencies and Scoping
Dependencies can themselves depend on other dependencies, forming a DAG. You can place a dependency at three scopes:
- Operation level – in the route signature, runs only for that endpoint.
- Router level – via
APIRouter(dependencies=[Depends(...)]), runs for every route in that router. - Global level – via
FastAPI(dependencies=[Depends(...)]), runs for every request.
For example, a get_current_user dependency that needs a database session can declare def get_current_user(db: Session = Depends(get_db)): and FastAPI will resolve the graph automatically.
Trade‑off: Sync vs Async Dependencies
FastAPI executes def dependencies in a thread pool to avoid blocking the event loop, while async def dependencies run directly on the loop. Use def with blocking libraries (e.g., psycopg2, standard SQLAlchemy) and async def with async drivers (e.g., databases, sqlalchemy.ext.asyncio) to keep the event loop free.
Verification and Limitation
OpenAPI documentation (/docs) automatically reflects dependencies: security schemes, query parameters, or headers required by a dependency appear as inputs for the endpoint. This lets you verify that your DI graph is wired correctly without writing tests.
Limitation: Deep chains (A → B → C → D) can obscure tracebacks and make debugging harder. Keep the dependency graph shallow and name dependencies clearly to maintain traceability.
Actionable Summary
- Use
yieldfor any resource that needs teardown (DB sessions, file handles, clients). - Place cross‑cutting concerns like API‑key validation at the router or global level to avoid repetition.
- Match the dependency’s function type (
defvsasync def) to the blocking nature of the underlying library.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.