FastAPI Dependency Injection
Learn FastAPI's dependency injection system: how it works, why it's useful, and how to use it in your APIs. This lesson covers the core concepts, step-by-step examples, and best practices.
Focus: using fastapi’s dependency injection system
You've built route handlers that work, but every time you need the current user, a database session, or a shared config, you copy-paste the same boilerplate into each endpoint. That duplication is a breeding ground for bugs, inconsistent behavior, and security holes. FastAPI's dependency injection (DI) system eliminates this pain: it lets you declare what an endpoint needs, and FastAPI automatically provides it — cleanly, testably, and with zero manual wiring. In this lesson, using fastapi’s dependency injection system, you'll move from repetitive handlers to elegant, reusable code that scales.
The problem this lesson solves
Imagine you have five endpoints that all need a database session. Without a plan, you'd write the same setup code five times: create a session, handle errors, close it. Multiply that by authentication, pagination, logging — and your codebase becomes a swamp of copy-pasted logic. Worse, a small change (like adding a new header) forces you to hunt down every endpoint and update it. This is the classic dependency management problem: how do you give each endpoint what it needs without hard-coding it?
For beginners, it's tempting to solve this with global variables or a helper function you call at the top of each handler. But globals break testing and hide state; helper functions still require you to remember to call them. FastAPI's answer is declarative: you describe your dependencies as parameters to your endpoint, and the framework takes care of the rest.
This lesson is step 13 in the FastAPI Backend Development path. By now you know routing and Pydantic models. DI is the glue that lets you build on those foundations — it's how you'll handle auth, database access, and shared logic in every serious FastAPI project.
Core concept / mental model
Think of FastAPI's dependency injection as a restaurant ordering system. Your endpoint is the customer at the counter; it doesn't cook anything itself. Instead, it says, "I'd like a database session, a current user, and a settings object." The kitchen — FastAPI's DI system — takes that order, prepares each item using its own recipe (the dependency function), and hands it over.
Here's the mental model in three parts:
- Dependency function: A plain Python function that does setup work and returns a value. It can itself depend on other dependencies (the kitchen uses sub-recipes).
- Parameter declaration: In your path operation, you add a parameter with a type hint that matches the return type of your dependency. FastAPI sees this and knows to call the function and pass the result.
- The
Depends()marker: This is the special signal that tells FastAPI, "This parameter isn't coming from the request body or query string — it's a dependency."
In FastAPI, a dependency is just a callable. It can be a function, a class, or even a generator. The system is entirely based on type hints and the Depends class from fastapi. That's all there is to it — no abstract containers, no service locator patterns, just Python.
Here's a tiny example to make it concrete:
from fastapi import FastAPI, Depends
app = FastAPI()
def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
Pro tip: The dependency function runs on every request that uses it. For expensive setup, use
lru_cacheor a generator withyieldto control lifecycle.
How it works step by step
Understanding the flow demystifies DI. Here's what happens when a request hits an endpoint with a dependency:
- FastAPI receives the request and looks at the path operation's parameters.
- It sees a parameter with a
Depends(...)default value — that's the trigger. - FastAPI resolves the dependency function: it analyzes that function's own parameters, recursively resolving any nested dependencies.
- It builds the dependency tree (in the right order), calls each function, and collects the results.
- It passes the resolved value as the argument to your endpoint.
- If a dependency is a generator (using
yield), FastAPI calls the cleanup code after the response is sent.
Most importantly, dependencies are cached within a single request. If two endpoints depend on the same dependency, it runs only once per request — this is efficient and predictable.
Why does this matter for you? Because it gives you separation of concerns. Authentication logic lives in one function; database session management in another; business rules stay in the endpoint. You test each piece in isolation.
The role of type hints
FastAPI uses type hints to know what to inject. If your dependency returns a User, and your endpoint declares user: User = Depends(get_current_user), you get a User object. The type hint also drives automatic documentation in Swagger UI — dependencies become visible, testable parameters in the docs.
Hands-on walkthrough
Let's build a real example. We'll create a small API with two practical dependencies: a database session provider (simulated) and an authentication dependency. This mirrors what you'll do in production.
First, define the database dependency using a generator to ensure cleanup:
from fastapi import FastAPI, Depends
from contextlib import asynccontextmanager
app = FastAPI()
class Database:
def __init__(self):
self.connected = True
def close(self):
self.connected = False
async def get_db():
db = Database()
try:
yield db
finally:
db.close() # Always runs, even on exceptions
Now an auth dependency that both validates a token and returns a user object:
from fastapi import Depends, HTTPException, status
def verify_token(authorization: str | None = None):
# In real life, validate JWT etc.
if authorization != "secret-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
return {"user_id": 42, "name": "Ada"}
def get_current_user(token_data: dict = Depends(verify_token)):
# This is a nested dependency!
return token_data
Finally, wire it all together in endpoints:
from fastapi import FastAPI, Depends
app = FastAPI()
@app.get("/profile")
async def get_profile(
db: Database = Depends(get_db),
user: dict = Depends(get_current_user)
):
return {"user": user, "db_status": db.connected}
@app.get("/items/{item_id}")
def get_item(item_id: int, db: Database = Depends(get_db)):
return {"item_id": item_id, "db_status": db.connected}
Run this with uvicorn main:app --reload. Visit http://localhost:8000/profile and you'll see {"detail":"Invalid token"}. Add the header Authorization: secret-token and you'll get:
{
"user": {"user_id": 42, "name": "Ada"},
"db_status": true
}
Notice how get_current_user itself depends on verify_token. FastAPI resolves both automatically. The database is injected fresh for each request and closed afterward — thanks to the yield generator pattern.
Using classes as dependencies
Sometimes a dependency needs to hold state or configuration. You can use a class instead of a function:
from fastapi import Depends
class Settings:
def __init__(self):
self.app_name = "MyAPI"
self.debug = False
@app.get("/info")
async def get_info(settings: Settings = Depends(Settings)):
return {"app": settings.app_name, "debug": settings.debug}
This is handy for dependency configuration. FastAPI even supports a shortcut: you can omit Depends() and just use the class as a type hint — FastAPI will automatically instantiate it.
Compare options / when to choose what
FastAPI gives you multiple ways to share code. Here's a comparison to help you choose:
| Approach | Pros | Cons | Use when… |
|---|---|---|---|
Depends() with a function |
Simple, testable, cached per request | Requires a function definition | Most cases: auth, DB, common params |
Generator (yield) dependency |
Cleanup guaranteed (DB close, file close) | Slightly more complex syntax | Managing resources that must be released |
| Class dependency | Encapsulates config, reusable | Adds boilerplate for tiny jobs | When dependency has methods/state |
| Global variable | Zero setup | Hard to test, hidden state, concurrency issues | Never — avoid it |
| Manual call in handler | Simple to understand | Duplicated code, error-prone | Only for one-off cases in a script |
Rule of thumb: Use a function with Depends() for 90% of your needs. Switch to a generator when you need cleanup (database sessions, file handles). Use a class when the dependency's logic is substantial.
Troubleshooting & edge cases
Even experienced devs hit these. Here are the common issues:
- Type mismatch: Your endpoint's parameter type hint doesn't match the dependency's return type. FastAPI will still pass the object, but your IDE and static checks will flag it. Always align the types.
- Missing
Depends(): If you forgetDepends(), FastAPI treats the parameter as a request body or query parameter — it won't inject your dependency. Error messages can be confusing, so verify the default value. - Generator dependencies flaky: If your
yieldappears after you try toreturnor raise, bad things happen. The generator must yield exactly once. - Circular dependencies: If
Adepends onB, andBdepends onA, FastAPI will raise an error about circular dependencies. Break the cycle by injecting a shared object instead. (Avoid this design!) - Dependencies run per request, not per call: If you test a function that uses a dependency directly (not through FastAPI), you might expect it to run once. It will run once per request — fine. But if you call the endpoint multiple times in a test, the dependency will run each time.
- Caching in tests: Use
dependency_overridesto replace a dependency with a mock. This is essential for testing; FastAPI'sapp.dependency_overridesdictionary lets you swap out dependencies easily.
What you learned & what's next
You've now mastered using fastapi's dependency injection system. You understand the core idea: declare dependencies as parameters and let FastAPI resolve them. You can implement practical dependencies like database sessions and auth, and you know how to choose between functions, generators, and classes. You've also seen how to troubleshoot common pitfalls.
This skill is foundational — you'll use DI in authentication, database sessions, pagination, and more. Next in the track, you'll dive into handling authentication and security, where DI becomes your main tool for protecting endpoints. Get ready to build secure APIs with confidence.
Before you move on
- Write a dependency that reads a header and returns a custom object.
- Create a generator dependency that logs start and end times.
- Use
app.dependency_overridesto mock a database in a test.
Practice recap
Build a small app with a get_user dependency that reads a custom header and returns a user object. Then create a generator dependency that logs request start and end times. Finally, use dependency_overrides to replace the user dependency with a mock in a test — you'll see how easy unit testing becomes.
Common mistakes
- Forgetting to add
Depends()as the default value — FastAPI treats the parameter as a request body or query param, and your dependency never runs. - Raising an HTTPException inside a generator dependency after
yield— cleanup code runs, but the exception behavior can surprise you; raise beforeyield. - Using a global mutable object instead of a dependency — leads to concurrency bugs and makes testing nearly impossible.
- Creating circular dependencies without a clear boundary — FastAPI raises an error; design your dependency graph to be acyclic.
Variations
- Use a class as a dependency with the
Dependskeyword, or even without it — FastAPI auto-instantiates if you use the class as a type hint. - Use
yieldto create context-manager-style dependencies that clean up after the response is sent. - Swap dependencies in tests via
app.dependency_overridesto mock external services like databases.
Real-world use cases
- Authenticating users with a JWT token and loading the user object from a database for every protected route.
- Managing a database session per request, ensuring it closes even when an exception is raised.
- Providing shared pagination and filtering parameters to multiple list endpoints without duplicating code.
Key takeaways
- FastAPI DI uses type hints and the
Depends()marker to inject callable results into endpoints. - Generator dependencies with
yieldguarantee cleanup for resources like database sessions. - Dependencies can themselves depend on others, building a clean, testable graph.
- Choose function dependencies for simple logic, generators for resource management, and classes for stateful setups.
- Always align return types between dependency and endpoint parameter to avoid subtle bugs.
- Use
dependency_overridesfor testing to replace real dependencies with mocks.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.