FastAPI has quickly become one of the most loved Python web frameworks, thanks to its speed, type‑safety, and developer‑friendly design. One of the core features that sets FastAPI apart is its powerful dependency injection system, which lets you write clean, reusable, and testable code with minimal boilerplate. In this guide we’ll walk through everything you need to know about FastAPI dependency injection—from the basics to advanced patterns—so you can harness its full potential in your next Python project.
What Is Dependency Injection in FastAPI?
In simple terms, dependency injection (DI) is a technique where a function (or class) receives the resources it needs from the outside rather than creating them internally. FastAPI’s DI system is built on Python’s type hints and the Depends class, allowing you to declare dependencies directly in your endpoint signatures.
- Reusability: Write a single piece of logic (e.g., database connection, authentication) and reuse it across many routes.
- Testability: Swap real implementations with mocks or stubs during unit tests without touching the endpoint code.
- Separation of concerns: Keep business logic separate from request‑handling, validation, and security concerns.
Basic Dependency Syntax
The most common way to declare a dependency is by using the Depends function as a default value for a parameter.
from fastapi import FastAPI, Depends
app = FastAPI()
def get_query_param(q: str = "default"):
return q
@app.get("/items/")
def read_items(query: str = Depends(get_query_param)):
return {"query": query}
In the example above, get_query_param is injected into read_items. FastAPI automatically calls the dependency, resolves its own parameters, and passes the result to the endpoint.
Key Points to Remember
- Dependencies can be functions or callable classes.
- FastAPI resolves dependencies **recursively**—if a dependency itself depends on another, FastAPI will handle the entire chain.
- The return value of a dependency becomes the value of the parameter that uses
Depends.
Dependency Injection for Common Scenarios
1. Database Sessions
Managing a database connection per request is a classic use case. Below is a typical pattern using SQLAlchemy’s SessionLocal factory.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from fastapi import Depends
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)
def get_db() -> Session:
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/")
def read_users(db: Session = Depends(get_db)):
return db.query(User).all()
Notice the use of yield inside the dependency. FastAPI treats it as a “dependency with cleanup”, automatically closing the session after the request finishes.
2. Authentication & Authorization
FastAPI’s DI system shines when handling security. You can create a reusable dependency that extracts the current user from a JWT token.
from fastapi import HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_current_user(token: str = Security(oauth2_scheme)):
# Decode token, verify, fetch user, etc.
user = decode_token(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials")
return user
@app.get("/profile/")
def read_profile(current_user: User = Depends(get_current_user)):
return {"username": current_user.username, "email": current_user.email}
By injecting get_current_user, every endpoint that needs authentication automatically receives the validated user object.
3. Request‑Scoped Caching
Sometimes you want to compute a value once per request and reuse it across multiple dependencies. FastAPI provides a request.state object that can be used for this purpose.
from fastapi import Request
def expensive_computation(request: Request):
if not hasattr(request.state, "expensive_result"):
request.state.expensive_result = compute_heavy_task()
return request.state.expensive_result
@app.get("/calc/")
def get_result(value: int = Depends(expensive_computation)):
return {"result": value}
Advanced Dependency Patterns
Using Classes as Dependencies
When your dependency requires internal state or several helper methods, a class can be a cleaner solution.
class Service:
def __init__(self, db: Session):
self.db = db
def get_item(self, item_id: int):
return self.db.query(Item).filter(Item.id == item_id).first()
def get_service(db: Session = Depends(get_db)):
return Service(db)
@app.get("/items/{item_id}")
def read_item(item_id: int, service: Service = Depends(get_service)):
item = service.get_item(item_id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return item
This pattern keeps your endpoint thin while encapsulating business logic inside the Service class.
Dependency Overriding for Testing
One of the biggest advantages of FastAPI DI is the ability to replace real dependencies with mocks during tests. FastAPI provides app.dependency_overrides for this purpose.
from fastapi.testclient import TestClient
def fake_get_db():
# Return a mock or in‑memory DB session
yield FakeSession()
app.dependency_overrides[get_db] = fake_get_db
client = TestClient(app)
def test_read_users():
response = client.get("/users/")
assert response.status_code == 200
assert response.json() == [] # Expect empty list from fake DB
After the test, you can clear the overrides with app.dependency_overrides.clear(), ensuring isolation between test cases.
Conditional Dependencies
FastAPI lets you create dependencies that only run under certain conditions, such as based on request headers or query parameters.
def check_feature_flag(request: Request):
if request.headers.get("X-Feature-Flag") != "enabled":
raise HTTPException(status_code=403, detail="Feature not enabled")
return True
@app.get("/beta/")
def beta_endpoint(allowed: bool = Depends(check_feature_flag)):
return {"status": "You have access to the beta feature!"}
Best Practices for FastAPI Dependency Injection
- Keep dependencies small and focused. A single responsibility makes them easier to test and reuse.
- Prefer functions for simple tasks. Use classes only when you need state or multiple related methods.
- Leverage
yieldfor resources that need cleanup. This ensures proper closing of DB connections, file handles, etc. - Document your dependencies. Use docstrings and type hints so IDEs can provide autocomplete and developers understand the contract.
- Group related dependencies. For example, create a
security.pymodule for all auth‑related functions, and adatabase.pymodule for DB utilities. - Use dependency overrides in tests, not monkey‑patching. This keeps your production code untouched and your test suite deterministic.
- Cache expensive results per request. Store them on
request.stateto avoid recomputation across multiple dependencies.
Performance Considerations
FastAPI’s DI system is designed to be lightweight, but it’s still worth understanding its impact on request latency.
- Dependency depth: Each additional layer adds a small overhead. Keep the chain reasonable (usually < 5 levels) for optimal speed.
- Yield‑based cleanup: Using
yieldintroduces a context‑manager‑like behavior. The overhead is negligible compared to the benefit of guaranteed cleanup. - Singleton vs. per‑request: For resources that are thread‑safe (e.g., a Redis client), instantiate them once at startup and inject the same instance. Avoid creating heavy objects on every request.
Putting It All Together – A Mini Project Example
Below is a concise example of a FastAPI app that demonstrates database access, authentication, and service‑layer logic—all wired together using dependency injection.
from fastapi import FastAPI, Depends, HTTPException, status, Security, Request
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# ---------- Database Setup ----------
SQLALCHEMY_DATABASE_URL = "sqlite:///./example.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
Base.metadata.create_all(bind=engine)
# ---------- Dependency: DB ----------
def get_db() -&
Leave a Reply