Managing Database Session Lifecycles with FastAPI Dependency Injection
Stop leaking database connections in FastAPI. Learn how to use 'Depends' and 'yield' to automate session lifecycles and decouple resource management from your business logic.
09 Aug 2025, 18:32 UTC

The Problem: Leaking Database Connections
A common failure point in FastAPI applications is the "leaky connection." When developers manually instantiate database sessions inside an endpoint, they often forget to close the connection in an except or finally block. This leads to connection pool exhaustion, where the application stops responding because no more slots are available in the database pool.
The solution is to move session management out of the business logic and into FastAPI's dependency injection system using the Depends mechanism. By using a generator function with the yield keyword, you can ensure that every request gets its own session and, more importantly, that every session is closed regardless of whether the request succeeded or crashed.
How Dependency Injection Decouples Logic
Dependency injection (DI) allows you to define a resource—like a database session or a current user object—once and "inject" it into any endpoint that needs it. In FastAPI, this is handled by the Depends() class.
Instead of the endpoint being responsible for creating the session, it simply declares that it requires a session. This follows the Single Responsibility Principle: the endpoint handles the HTTP request and response, while the dependency handles the resource lifecycle.
The Power of the Yield Statement
When a dependency uses yield instead of return, FastAPI treats it as a context manager. The code before the yield runs before the endpoint executes. The code after the yield runs after the response has been delivered to the client. This is the ideal place for session.close() or db.rollback() logic.
Implementation: Shared Session Management
Below is a practical implementation using SQLAlchemy. This example assumes you have a SessionLocal class configured as your session factory.
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from .database import SessionLocal # Your SQLAlchemy session factory
app = FastAPI()
# 1. Define the dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
# This executes AFTER the response is sent
db.close()
# 2. Inject the dependency into the path operation
@app.get("/items/{item_id}")
def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return item
Execution Details
- Where to run: This code resides in your main application or router files.
- Permissions: The application process must have network access to the database.
- Expected Check: Use your database's process monitor (e.g.,
pg_stat_activityfor PostgreSQL) to verify that connections are released immediately after the request finishes. - Risk: If
get_dbis defined asasync defbut uses a synchronous database driver (like standardpsycopg2), you may block the event loop, killing performance. Usedeffor synchronous drivers.
Scaling with Nested Dependencies
Dependencies can depend on other dependencies. For example, you might have a get_current_user dependency that requires the get_db dependency to look up the user in the database.
def get_current_user(token: str, db: Session = Depends(get_db)):
user = db.query(User).filter(User.token == token).first()
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
return user
@app.get("/me")
def read_user_me(current_user: User = Depends(get_current_user)):
return current_user
FastAPI resolves this graph automatically: it calls get_db, passes the session to get_current_user, passes the user to the endpoint, and then winds back up to close the database session.
Trade-offs and Limitations
While powerful, DI can introduce "hidden" complexity. If you nest dependencies five or six levels deep, tracing the execution flow during a bug hunt becomes difficult. Furthermore, dependencies are executed before the endpoint logic; if a dependency performs a slow, blocking I/O operation, the user experiences latency before your actual business logic even starts.
Verification Step
To verify the teardown logic is working, add a print statement or a log entry immediately after the yield db line. Trigger a request and observe that the log appears after the HTTP response is received by your browser or API client.
Actionable Closing
Stop manually managing session.close() inside your endpoints. Move your resource lifecycles into yield-based dependencies. This not only prevents memory and connection leaks but also makes your endpoints easier to unit test, as you can easily override the get_db dependency with a mock or a test database during your test suite execution.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.