Managing Database Sessions in FastAPI with Dependency Injection
Stop leaking database connections in FastAPI. Learn how to use 'yield' dependencies to automate session setup and teardown for every request.
12 Mar 2026, 22:56 UTC

The Leaking Connection Problem
A common failure point in FastAPI applications is the \"leaked\" database connection. When a developer manually opens a database session inside a route handler but forgets to close it in an exception block, the connection remains open. Over time, the database reaches its maximum connection limit, and the application stops responding to new requests.
The solution is to move resource management out of the business logic and into FastAPI's Dependency Injection (DI) system. By using the Depends mechanism with a yield statement, you can ensure that every request gets a fresh session and, more importantly, that every session is closed regardless of whether the request succeeded or crashed.
How the Yield Dependency Works
FastAPI dependencies are not just for authentication. When you use yield instead of return in a dependency function, FastAPI treats it as a context manager. The code before the yield runs before the path operation, and the code after the yield runs after the response has been delivered to the client.
The Request Lifecycle
- Setup: FastAPI executes the dependency logic up to the
yield. - Injection: The yielded value (e.g., a database session) is passed into the route function.
- Execution: The route function processes the request and returns a response.
- Teardown: FastAPI resumes the dependency function to execute the cleanup code.
Implementing a Request-Scoped Session
Below is a practical implementation using SQLAlchemy. This pattern ensures that the session is tied to the lifecycle of a single HTTP request.
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, Session, declarative_base
# Database Setup
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)
Base = declarative_base()
# Model
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
Base.metadata.create_all(bind=engine)
app = FastAPI()
# The Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
# This block runs after the response is sent
db.close()
@app.get("/users/{user_id}")
def read_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
Execution and Verification
To run this, you need fastapi, uvicorn, and sqlalchemy installed. Run the server using uvicorn main:app --reload. To verify the teardown logic is working, you can add a print statement inside the finally block. You will notice the print occurs after the browser or API client receives the JSON response.
The Trade-off: Sync vs. Async Dependencies
A critical decision when implementing dependencies is whether to use def or async def. This choice changes how FastAPI handles the execution thread.
| Dependency Type | Execution Behavior | Best Use Case |
|---|---|---|
def get_db() | Run in a separate thread pool to avoid blocking the event loop. | Blocking I/O (e.g., standard SQLAlchemy, psycopg2). |
async def get_db() | Run directly on the main event loop. | Non-blocking I/O (e.g., SQLAlchemy 2.0 async, Motor, httpx). |
Risk: If you define a dependency as async def but perform a blocking database call inside it, you will freeze the entire application for all users until that call completes. If your database driver is synchronous, always use def.
Limitations of Nested Dependencies
FastAPI allows you to nest dependencies (e.g., get_current_user depends on get_db). While powerful, deep nesting can make debugging difficult because the execution order is determined by a dependency graph. If a dependency high up the chain fails, the subsequent dependencies and the route handler are never reached. Always keep your dependency chains shallow to maintain visibility into the request flow.
Closing Action
Review your current route handlers. If you see db = SessionLocal() and db.close() inside your functions, refactor them into a yield dependency. This removes boilerplate and guarantees resource cleanup, making your application significantly more resilient to connection leaks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.