FastAPI Dependency Injection (DI) Guide
-
Jason Yang - 08 Jan, 2026
- Updated 08 Jan, 2026
- Views —
FastAPI’s dependency injection system is not just a way to pass objects around. It is a resource management engine built around the request lifecycle.
This guide explains how FastAPI DI actually works, how it differs from frameworks like Spring, and how to design clean, testable architectures using Depends().
1. Core Concept: Depends()
Depends() tells FastAPI to resolve a dependency before executing a path operation and inject the result as a function argument.
How this differs from Spring
- Spring resolves dependencies at application startup and stores them in an application context.
- FastAPI resolves dependencies per HTTP request, building a dependency graph dynamically for each request.
from fastapi import Depends
def get_query_param(q: str | None = None):
return q
@app.get("/items")
async def read_items(query: str = Depends(get_query_param)):
return {"query": query}
2. Dependency Chains
One of FastAPI’s most powerful features is that dependencies can depend on other dependencies. This allows you to express complex architectures in a clean, layered way.
Example: Clean Architecture with Dependency Chains
The following example reflects a common and practical rule:
- Heavy resources → singleton-like, reused
- Light objects → created per request
# 1. Infrastructure layer (Heavy – singleton)
async def get_vertex_ai_client() -> VertexAIClient:
# Module-level singleton access
return await VertexAIClient.get_instance()
# 2. Repository layer (Light – per request)
def get_user_repository() -> UserRepository:
return UserRepository()
# 3. Service layer (dependency chain)
async def get_user_service(
repo: UserRepository = Depends(get_user_repository),
ai_client: VertexAIClient = Depends(get_vertex_ai_client)
) -> UserService:
return UserService(repo, ai_client)
# 4. API layer
@app.post("/users/analyze")
async def analyze_user(service: UserService = Depends(get_user_service)):
return await service.perform_analysis()
FastAPI resolves this chain from the bottom up, injecting fully constructed dependencies into higher layers.
3. Request-Scoped Caching
Within a single HTTP request, FastAPI caches dependency results.
- If multiple services depend on the same function (e.g.,
get_db_session), it is executed only once. - The same instance is reused throughout the request.
- This prevents unnecessary database connections and repeated computations.
You can disable this behavior when needed:
Depends(get_db_session, use_cache=False)
4. Resource Lifecycle Management with yield
FastAPI supports setup and teardown logic in dependencies using yield, similar to Java’s try-with-resources.
| Phase | When it runs | Java analogy |
|---|---|---|
Before yield | Before endpoint logic | Connection conn = dataSource.getConnection() |
| Yielded value | Injected into endpoint | return conn |
After yield | After response is sent | conn.close() (finally) |
async def get_db():
db = DatabaseSession()
try:
yield db
finally:
await db.close()
This guarantees cleanup even if an exception occurs.
5. Practical Best Practices
Interface-based injection
While Python supports duck typing, defining clear interfaces using Protocol or ABC makes large codebases easier to reason about and refactor.
Dependency overrides for testing
FastAPI makes it trivial to replace real dependencies with mocks in tests.
app.dependency_overrides[get_vertex_ai_client] = lambda: MockAIClient()
This enables fast, isolated, and deterministic tests.
Control dependency depth
Deep dependency chains can become hard to debug. A common and effective guideline is:
API → Service → Repository / Client
Three layers are usually enough.
Summary
- Heavy resources (clients): async, reused, typically singleton-like
- Light objects (services/repositories): created per request
- Cleanup logic: use
yieldfor guaranteed teardown - Architecture: express dependencies as a directed acyclic graph (DAG)
Final Thoughts
FastAPI DI is not just a convenience feature.
It is a first-class architectural tool.
When used correctly, it enables:
- Clear separation of concerns
- Predictable resource lifecycles
- Highly testable application design