Dependency Injection (Depends)
Scenario
Your endpoint needs to query the database and verify the user is logged in. You could write the auth verification and DB connection code inside the route function itself, but then you'd have to copy-paste it for the next 50 endpoints.
Overview
Dependency injection (DI) via `Depends` in FastAPI is a mechanism to declare what a route needs—like a database connection or current user—and have the framework automatically provide it.
It separates setup code from business logic, prevents massive code duplication across routes, and makes endpoints trivially easy to test by overriding the dependencies.
Where used: FastAPI route definitions, Authentication and authorization flows, Database session management
Why learn this
- Virtually every FastAPI route beyond a 'Hello World' needs some shared state or setup.
- It is the standard FastAPI way to handle authentication and database sessions.
- Understanding Depends is key to writing modular and testable backend code.
Mental model
Depends is a restaurant kitchen: the waiter doesn't cook, he declares what the dish needs.
When an endpoint declares a parameter with `Depends(get_db)`, FastAPI pauses before running the endpoint. It runs `get_db()`, takes whatever it returns, and passes it into the endpoint parameter. The endpoint just declares what it needs; FastAPI handles the fetching.
Deep dive
Without dependency injection, an endpoint has to do its own chores: open a database connection, check the headers for a token, validate the token, and handle errors if it's missing. This mixes 'setup' with 'business logic' and leads to massive code duplication across routes.
Doing setup inside the route creates bloated, repeated code.
FastAPI solves this with the `Depends` function. You write a separate function that does the chore (e.g., `def get_db(): ...`). In your endpoint, you add a parameter: `db: Session = Depends(get_db)`. Before calling your endpoint, FastAPI automatically calls `get_db()`, takes its result, and injects it into the `db` parameter.
Depends separates 'getting what you need' from 'using it'.
Dependencies can do more than return values; they can enforce rules. If a dependency raises an `HTTPException` (e.g., an invalid token), the request stops immediately. The endpoint code never runs, guaranteeing that if the endpoint *does* run, the dependency succeeded.
Dependencies act as gatekeepers for authentication and validation.
Because dependencies are declared in the function signature, testing becomes incredibly easy. In your test, you can tell FastAPI, 'Whenever a route asks for `get_db`, give it `get_test_db` instead.' This allows you to swap out real databases or auth systems for mocked ones without changing the endpoint code.
DI makes testing trivial by allowing you to override dependencies.
Code examples
The bad way: Manual setup inside the endpoint
from fastapi import APIRouter, Header, HTTPException
router = APIRouter()
@router.get('/users/me')
def get_current_user(authorization: str = Header(None)):
# 1. Setup/Auth logic mixed into the endpoint
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Invalid token')
token = authorization.split(' ')[1]
user = fake_verify_token(token)
# 2. Actual business logic
return {'user': user}
Here, the endpoint does its own authentication. If you add 10 more routes that need the current user, you have to copy-paste the auth logic into every single one.
The FastAPI way: Using Depends
from fastapi import APIRouter, Depends, Header, HTTPException
# The dependency function
def get_current_user(authorization: str = Header(None)):
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Invalid token')
token = authorization.split(' ')[1]
return fake_verify_token(token)
router = APIRouter()
# The endpoint just asks for what it needs
@router.get('/users/me')
def read_user_profile(user: dict = Depends(get_current_user)):
return {'user': user}
The `get_current_user` function handles the complexity. The endpoint simply declares it needs a user via `Depends`. FastAPI calls the dependency and injects the result.
Overriding dependencies in tests
from fastapi.testclient import TestClient
from main import app, get_current_user
# A fake dependency for testing
def mock_get_current_user():
return {'id': 1, 'username': 'test_user'}
# Tell the app to use the mock instead of the real dependency
app.dependency_overrides[get_current_user] = mock_get_current_user
client = TestClient(app)
def test_read_user_profile():
# The request doesn't need a real auth header now!
response = client.get('/users/me')
assert response.status_code == 200
assert response.json() == {'user': {'id': 1, 'username': 'test_user'}}
`app.dependency_overrides` lets you swap out complex dependencies (like database connections or auth calls) with simple test doubles, meaning you don't have to change your application code to test it.
Key points
- Functions or Classes: A dependency can be any callable: a regular function, an async function, or a class.
- Execution Order: Dependencies run BEFORE the route handler. If a dependency raises an exception, the handler never executes.
- Caching: By default, if multiple dependencies in the same request require the same sub-dependency, FastAPI only executes it once and caches the result for that request.
- Overrides: You can replace any dependency globally during tests using `app.dependency_overrides`.
Common mistakes
- Calling the dependency instead of passing it to Depends: Writing `user = Depends(get_current_user())` instead of `user = Depends(get_current_user)`. You must pass the function reference, not call the function yourself. FastAPI needs to call it at request time.
- Using mutable global state instead of dependencies: Creating a global database connection object and importing it into routes. This breaks in async servers and makes testing impossible because you can't easily isolate the database for each test.
Recall questions
- What is the main benefit of using `Depends` in FastAPI?
- How do you mock a dependency for a test in FastAPI?
- If a dependency raises an `HTTPException`, what happens to the endpoint code?
Questions & answers
What is dependency injection in FastAPI?
It's a mechanism where you declare what an endpoint needs (like a DB session or a user) in the function signature using `Depends()`. The framework automatically runs the required setup code and passes the result into the endpoint.
You are writing tests for an endpoint that requires an expensive third-party API call to verify a user token. How do you test the endpoint's logic without hitting the API?
I would put the API call in a FastAPI dependency, then use `app.dependency_overrides` in my test suite to replace that dependency with a mock function that returns a dummy user.
Continue learning
Previous: Routers & App Structure
Previous: async vs def Endpoints
Next: Middleware & CORS