Manage Request-Scoped Resources in FastAPI with Depends
Use FastAPI Depends with generator dependencies to manage database sessions and auth per request, then override them in tests without changing route code.
13 Mar 2026, 10:13 UTC

The problem: every route manages its own database session
You inherit a FastAPI service where each route opens a SQLAlchemy session, queries data, and hopes it closes the session before the response finishes. One route forgets, and under load the connection pool exhausts. Another route repeats the same token validation in three endpoints. When it is time to test, you either point the tests at a real database or duplicate route logic to avoid it.
The useful takeaway: declare request-scoped resources such as database sessions and authentication checks as FastAPI dependencies using Depends(). Use a generator function with yield so cleanup runs after the response, and swap those dependencies out in tests with app.dependency_overrides instead of rewriting routes.
A request-scoped dependency with yield
Depends() tells FastAPI to resolve a callable and inject its return value into a route parameter. When the callable is a generator, FastAPI runs the code before yield before the route handler, runs the route, then runs the code after yield after the response is sent—even if the handler raises an exception. This makes it a natural place to open and close a database session.
FastAPI’s dependency contract has been stable across recent minor releases with Pydantic v1 or v2.
from fastapi import Depends, FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
app = FastAPI()
engine = create_engine("sqlite:///./app.db")
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
The finally block guarantees the session closes. FastAPI treats get_db as a dependency factory, so each request receives its own session by default.
Inject the session into a route
from fastapi import Depends
from sqlalchemy.orm import Session
@app.get("/items/")
def read_items(db: Session = Depends(get_db)):
return db.query(Item).all()
The route never creates or closes the session itself. It only declares what it needs, and FastAPI handles resolution and teardown.
Replace real dependencies in tests
FastAPI exposes dependency_overrides on the application instance. In a test fixture, map the real dependency to a test implementation. Routes keep using Depends(get_db), but during the test they receive the test session instead.
from fastapi.testclient import TestClient
def test_read_items_uses_test_session():
test_db = SessionLocal()
app.dependency_overrides[get_db] = lambda: test_db
try:
client = TestClient(app)
response = client.get("/items/")
assert response.status_code == 200
finally:
app.dependency_overrides.clear()
test_db.close()
Clearing overrides in finally matters because the dictionary lives on the app instance and can leak into other tests if you forget.
Trade-offs and surprises
- Per-request caching: FastAPI caches the result of a dependency within a single request by default. If you call
Depends(get_db)more than once, you get the same session. Passuse_cache=Falseif you need a fresh instance. - Early aborts: Raising
HTTPExceptioninside a dependency stops the request before the route runs. That is useful for authentication, but it can make error flows harder to trace across nested dependencies. - Blocking I/O: An async dependency that performs blocking database calls without
run_in_threadpoolcan block the event loop and reduce concurrency.
Verify before you ship
- Run your application locally and open
/openapi.json. Confirm that injected dependencies such as the database session do not appear as query or body parameters unless you intended them to be client inputs. - Add a print statement before and after
yieldinget_db, then make a request and confirm the teardown message appears after the response is handled. - Run your test suite with the override in place and assert that the route returns test data, not production data.
Bottom line
Pick one request-scoped resource—usually a database session or an authentication check—and extract it into a dependency with Depends and yield. Then write one test that overrides it. Your routes become thinner, your cleanup becomes predictable, and your tests no longer need a production database to exercise real route logic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.