What is Dependency Injection?¶
Imagine you’re writing an API that needs to fetch user information. This information might come from a database, cache, or authentication service. If every API endpoint writes its own code to fetch user info, it’s redundant and makes changes error-prone (e.g., switching databases).
Dependency Injection (DI) is the core idea: “Inject” dependencies (like database connections or user data) into functions automatically, instead of having the function create or retrieve them itself. This makes code more reusable, decoupled, and easier to maintain.
In FastAPI, the key tool for implementing DI is Depends—it acts as a “mediator” to deliver required dependencies to the function.
Basic Depends in FastAPI¶
Using Depends involves two steps: defining the dependency function and declaring the dependency in the path function.
1. Define the Dependency Function¶
The dependency function “produces” the object you need (e.g., a database connection). For example, a mock database connection:
from fastapi import FastAPI, Depends
# Dependency function: Returns a database connection object
def get_db():
# Returns a mock database connection (real projects use actual connections)
return {"connection": "MySQL"} # Example connection dict
2. Use Depends in Path Functions¶
Declare dependencies in path functions with Depends(dependency_function). FastAPI automatically calls the dependency function and passes its result:
app = FastAPI()
@app.get("/users/me")
def read_current_user(db: dict = Depends(get_db)):
# `db` is the object returned by `get_db()`
return {"db_connection": db, "message": "Current user info"}
When accessing /users/me, FastAPI first calls get_db() to get the database connection, then passes it as the db parameter to read_current_user.
Parameter Passing in Dependency Injection¶
Dependency functions can accept parameters (path parameters, query parameters, etc.), which FastAPI automatically resolves.
Example: Fetch User Info by ID¶
Suppose you need to query user info by a path parameter user_id, with the user data dependent on the database connection:
from fastapi import Depends, HTTPException
# Mock user data
mock_users = {1: {"id": 1, "name": "Alice"}, 2: {"id": 2, "name": "Bob"}}
# Dependency function: Queries user by `user_id` (from path parameter)
def get_user(user_id: int = Depends()):
if user_id not in mock_users:
raise HTTPException(status_code=404, detail="User not found")
return mock_users[user_id]
# Path function: Declares dependency
@app.get("/users/{user_id}")
def read_user(user: dict = Depends(get_user)):
return {"user": user}
Here:
- get_user uses Depends() to capture the path parameter user_id.
- If user_id is invalid, HTTPException returns a 404 error.
Advanced Usage¶
Dependency injection supports nested dependencies, caching, async operations, and parameter validation.
1. Nested Dependencies (Dependency of Dependencies)¶
A dependency can rely on another dependency. FastAPI resolves them in order:
def get_db():
return {"connection": "MySQL"} # Step 1: Get database connection
def get_user_by_db(db: dict = Depends(get_db), user_id: int = 1):
# Step 2: Use the database connection to query user (simplified example)
if user_id not in mock_users:
raise HTTPException(status_code=404, detail="User not found")
return mock_users[user_id]
@app.get("/users/{user_id}")
def read_user(user: dict = Depends(get_user_by_db)):
return {"user": user}
Execution Order: FastAPI first calls get_db() to get the connection, then passes it to get_user_by_db to fetch the user.
2. Dependency Caching (Singleton Pattern)¶
To reuse dependencies (e.g., database connection pools), use lru_cache for “singleton” behavior (avoids repeated initialization):
from functools import lru_cache
@lru_cache() # Caches the result of the dependency function
def get_db():
print("Initializing database connection...") # Only prints once
return {"connection": "MySQL"}
@app.get("/users/me")
def read_user(db: dict = Depends(get_db)):
return {"db": db}
Calling /users/me multiple times will only print “Initializing database connection…” once, as subsequent calls reuse the cached connection.
3. Async Dependencies¶
If the dependency function is asynchronous (e.g., async database calls), Depends supports it:
import asyncio
async def get_async_db():
await asyncio.sleep(0.1) # Simulate async database operation
return {"connection": "AsyncMySQL"}
@app.get("/async/users")
async def async_read_user(db: dict = Depends(get_async_db)):
return {"async_db": db}
Note: Only async path functions (using async def) can handle async dependency results.
4. Dependency Parameter Validation¶
Combine with Pydantic models to automatically validate parameters:
from pydantic import BaseModel
class UserFilter(BaseModel):
user_id: int = 1 # Defines parameter type and default value
def get_user_filter(filter: UserFilter = Depends()):
return filter # Returns validated parameters
@app.get("/validated/users")
def validated_user(filter: UserFilter = Depends(get_user_filter)):
return {"user_id": filter.user_id}
If a request parameter violates UserFilter (e.g., non-integer user_id), FastAPI returns a 422 Validation Error.
Core Advantages of Dependency Injection¶
- Code Reusability: Share dependencies across multiple path functions.
- Decoupling: Path functions focus on business logic, not dependency retrieval.
- Testability: Replace real dependencies with mocks during testing.
- Extensibility: Add new dependencies by modifying only the dependency function, not all calling code.
Summary¶
Depends is FastAPI’s core tool for managing dependencies. From basic usage to advanced scenarios (nested dependencies, async, caching), mastering Depends keeps your API code clean, maintainable, and scalable.
Next time you build a FastAPI app, ask: What repeated dependency logic can be extracted with Depends? This will make your code more elegant and powerful!