Dependency Injection
Understand the power of dependency injection in FastAPI for managing dependencies in your applications.
Content
Using Dependencies
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Using Dependencies in FastAPI — The Sequel You Actually Need
"Dependencies are not magic — they're delegation with manners." — Your future, less tired self.
You already learned what dependencies are and how to create them. You also know a bit about request/response handling (validation, serialization, etc.). Now we're stepping out of the lab and into the real world: how to use dependencies effectively in your routes, routers, and app-level logic. Buckle up. This is the part where your code gains dignity and fewer bugs.
Why this matters (without repeating the basics)
You used dependencies to extract logic out of endpoints. Great. Now learn how to:
- Inject values into path functions cleanly (and repeatedly) with minimal ceremony
- Compose dependencies (dependencies that depend on other dependencies)
- Control lifetime and cleanup (database sessions, connections)
- Scope dependencies at route/router/app level
- Use dependency caching and opt out when needed
This is the bridge between "valid request body + nice response" and "real application behavior": authorization, DB sessions, telemetry, and more.
Quick reminder: signature-style injection
FastAPI wires dependency functions into your path operation parameters using Depends. Example pattern:
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
def get_token_header(x_token: str | None = None):
if x_token != "expected":
raise HTTPException(status_code=400, detail="X-Token header invalid")
return x_token
@app.get("/items/")
def read_items(token: str = Depends(get_token_header)):
return {"token": token}
This is still the correct shape — you declare what you need; FastAPI gives it to you.
1) Dependencies that return useful objects (e.g., DB sessions)
Common pattern: create a dependency that yields a resource, ensuring cleanup.
from contextlib import contextmanager
from fastapi import Depends
# Example using yield for setup/teardown
async def get_db():
db = create_db_session()
try:
yield db
db.commit()
except:
db.rollback()
raise
finally:
db.close()
@app.post("/items/")
async def create_item(item: ItemCreate, db=Depends(get_db)):
db_item = db.add_item(item)
return db_item
Key: dependencies using yield can wrap setup and teardown logic. This ties into request/response handling because when you yield, FastAPI ensures teardown after the response is finished.
2) Sub-dependencies (dependency composition)
Dependencies can depend on other dependencies. This helps you build small, testable pieces.
def get_token(x_token: str = Header(None)):
if not x_token:
raise HTTPException(401)
return x_token
def get_current_user(token: str = Depends(get_token), db = Depends(get_db)):
return db.get_user_by_token(token)
@app.get("/me")
def read_me(user = Depends(get_current_user)):
return user
FastAPI will resolve get_token -> get_current_user -> read_me in the correct order.
3) Dependency caching per request (and how to turn it off)
If the same dependency function is used multiple times in a single request (e.g., as a param and as a sub-dependency), FastAPI will call it only once by default — caching per request. This avoids repeated DB connections or token checks.
If you deliberately want the dependency to run multiple times, use Depends(..., use_cache=False).
def get_random():
return uuid4()
@app.get("/a")
def a(x=Depends(get_random), y=Depends(get_random)):
# x == y because of request-level caching
return {"x": x, "y": y}
@app.get("/b")
def b(x=Depends(get_random), y=Depends(lambda: Depends(get_random), use_cache=False)):
# example showing how you'd disable cache (syntax simplified)
pass
(Yes, you almost never actually need to disable caching — but it's good to know.)
4) Router- and app-level dependencies
You can attach dependencies to an APIRouter or the app. This is great for middleware-like behaviors (authorization, telemetry, headers).
from fastapi import APIRouter
router = APIRouter(prefix="/admin", dependencies=[Depends(verify_admin)])
@router.get("/reports")
def reports():
return {"reports": "secret"}
app.include_router(router)
Important: dependencies attached here run for every endpoint in that router, but their return values are not available as parameters to your path functions. Use per-route Depends when you need the returned object inside the function.
5) Using Request and Response inside dependencies
Remember request/response handling? Dependencies can accept FastAPI/Starlette objects (Request, Response) just like path functions do:
from fastapi import Request, Response
def add_custom_header(response: Response):
response.headers["X-App-Version"] = "1.2.3"
@app.get("/status", dependencies=[Depends(add_custom_header)])
def status():
return {"status": "ok"}
This is how you can mutate the response (cookies, headers) from a dependency.
6) Class-based dependencies (stateful dependencies)
Want a bit of state or configuration? Use call in a class.
class AuthChecker:
def __init__(self, role: str):
self.role = role
def __call__(self, token: str = Depends(oauth2_scheme)):
user = check_token(token)
if user.role != self.role:
raise HTTPException(403)
return user
@app.get("/admin")
def admin_route(user = Depends(AuthChecker("admin"))):
return {"admin": user.name}
This pattern is perfect when you want small configurable dependency factories.
7) Errors, ordering, and best practices
- Put heavy-lifting validations (auth, DB) in dependencies — keeps endpoints readable.
- Keep dependencies small and focused. Compose them if needed.
- Use dependency caching to avoid repeated work.
- Use yields for resources that must be cleaned up (DB sessions, file handles).
- If you need the returned value for the path function, inject via parameter; if you're just doing side-effects (e.g., logging), use router/app dependencies.
Quick table: Where to use what
| Use-case | Prefer | Why |
|---|---|---|
| Return object to path function | Parameter Depends | You get the object as a parameter |
| Always run for a group of endpoints | Router/app dependencies | DRY for shared checks |
| Setup/teardown resource | yield dependency | Ensures cleanup after response |
| Configurable behavior | Class-based dependency | Clean API for config |
Final thoughts (the spicy part)
Dependencies in FastAPI are the neat little assistants your endpoints deserve. They let you separate concerns — validation and serialization (remember request/response handling) remain in the endpoint signature while business logic, auth, and resource management live in testable, reusable helpers.
Use them to make your code calmer, your tests simpler, and your debugging sessions 30% less dramatic. Or at least 10%.
Key takeaways:
- Use Depends to declare needs — FastAPI fulfills them.
- Compose small dependencies into bigger capabilities.
- Use yield for lifecycle control (setup/teardown).
- Depend on Request/Response if you need to mutate or inspect them.
- Attach dependencies to routers when you want middleware-style behavior.
Now go refactor that monster endpoint into elegant LEGO pieces. Your future self (and coworkers) will send you virtual cookies.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!