Managing Request State with FastAPI Dependency Injection
Learn how to use FastAPI's Dependency Injection system to manage database sessions and request state without leaking resources or duplicating code.
27 Jul 2025, 15:02 UTC

The Problem: Leaky Resource Management
When building an API, you often need shared resources—database sessions, authentication tokens, or configuration settings—available across multiple endpoints. The naive approach is to initialize these globally or instantiate them inside every route handler. Global instances create threading issues and make testing impossible, while manual instantiation leads to repetitive boilerplate and forgotten connection closures.
FastAPI solves this using Depends, a dependency injection (DI) system that allows you to define a resource once and "inject" it into any route that needs it. The key takeaway is that FastAPI manages the lifecycle of these dependencies, ensuring they are created when a request starts and cleaned up when it ends.
Hierarchical Dependencies and Scoping
FastAPI dependencies aren't just flat lists; they can be nested. A dependency can depend on another dependency, creating a graph of requirements. For example, a get_current_user dependency might first depend on a get_db dependency to verify the user in the database.
You can apply these dependencies at three different levels depending on your needs:
- Route Level: Specific to one endpoint (e.g., a password change route requiring an admin check).
- Router Level: Applied to all routes within a specific
APIRouter(e.g., all/usersendpoints requiring authentication). - Global Level: Applied to the entire
FastAPIapp instance, affecting every single request.
Implementing a Managed Database Session
The most common use case for DI is managing a database session. By using a generator function with yield, you can ensure that the session is closed regardless of whether the request succeeded or raised an exception.
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
# Setup SQLAlchemy (Assume SQLite for this example)
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
app = FastAPI()
# The Dependency: A generator that manages the session lifecycle
async def get_db():
db = SessionLocal()
try:
yield db
finally:
# This block runs after the response is sent
db.close()
@app.get("/items/{item_id}")
async def read_item(item_id: int, db: Session = Depends(get_db)):
# The 'db' variable is injected by FastAPI
return {"item_id": item_id, "status": "fetched from db"}
Execution Details: Run this code using uvicorn main:app. The get_db function is executed by the framework. The code before yield runs before the route handler; the code after yield runs after the response is delivered to the client.
Trade-offs: Sync vs. Async and Complexity
While powerful, the DI system has specific behaviors that can impact performance if misunderstood:
The Sync-Async Divide
If you define a dependency as a standard def (synchronous), FastAPI runs it in a separate thread pool to avoid blocking the main event loop. If you define it as async def, it runs directly on the event loop. If your dependency performs heavy I/O (like a legacy database driver) and you mark it async def, you will freeze the entire API for all users.
The Complexity Trap
Deeply nested dependencies (A depends on B, which depends on C, etc.) can make debugging difficult. When an error occurs, the stack trace will include the entire resolution chain. To keep the system maintainable, aim for a shallow dependency graph and avoid using global app-level dependencies for logic that only 10% of your routes actually use.
Verification and Results
To verify that your dependencies are working as expected, check the following:
- OpenAPI Documentation: Visit
/docs. Dependencies that involve security (like OAuth2) will automatically appear as "Authorize" buttons and required headers in the Swagger UI. - Lifecycle Logging: Add a print statement before and after the
yieldin your dependency. You should see the "closing" log only after the HTTP response has been sent. - State Isolation: Ensure that two concurrent requests receive different session instances by logging the
id(db)inside the route handler.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.