8.6. Dependency Injection
Table of Contents
Why Dependency Injection Matters in FastAPI
In FastAPI, dependency injection is the core mechanism that connects your route functions with everything they need, such as database sessions, configuration, authentication, and more. You already know how to write basic routes. This chapter explains how FastAPI’s dependency system lets you keep those routes clean, testable, and reusable.
Dependency injection, or DI, lets you declare what your function needs, and FastAPI takes care of how to provide it.
FastAPI uses the Depends class to implement DI. You will see it everywhere in real-world FastAPI projects.
The Basic Idea: `Depends`
In plain terms:
- A dependency is a function that returns something useful, for example a database session or the current user.
- You declare the dependency in your path operation or other dependency function using
Depends. - FastAPI calls the dependency function for you and injects its return value into your function.
A minimal example:
from fastapi import FastAPI, Depends
app = FastAPI()
def get_message():
return "Hello from dependency"
@app.get("/hello")
def read_hello(message: str = Depends(get_message)):
return {"message": message}Here:
get_messageis a dependency function.message: str = Depends(get_message)tells FastAPI: “Before callingread_hello, callget_messageand put its result intomessage.”- The client just calls
/hello. It never knows about the dependency chain.
You can think of Depends as a declarative way to say “I need this”.
Function Dependencies
Any normal Python function can be a dependency. It can:
- Take parameters (which can themselves be dependencies).
- Return any value or object.
- Have side effects (although you should keep them predictable).
Example: a dependency that reads a setting from environment variables.
import os
from fastapi import FastAPI, Depends
app = FastAPI()
def get_settings():
db_url = os.getenv("DB_URL", "sqlite:///./test.db")
debug = os.getenv("DEBUG", "false").lower() == "true"
return {"db_url": db_url, "debug": debug}
@app.get("/settings")
def read_settings(settings: dict = Depends(get_settings)):
return settingsFastAPI:
- Calls
get_settings. - Takes its return value.
- Passes it to the
settingsargument inread_settings.
You do not call get_settings yourself.
Combining Dependencies
Dependencies can depend on other dependencies. This builds a dependency tree.
from fastapi import FastAPI, Depends
app = FastAPI()
def get_app_name():
return "MyApp"
def get_greeting(app_name: str = Depends(get_app_name)):
return f"Welcome to {app_name}"
@app.get("/greet")
def greet(message: str = Depends(get_greeting)):
return {"greeting": message}
Execution order for /greet:
- FastAPI sees
message: str = Depends(get_greeting). - To call
get_greeting, it must provideapp_name: str = Depends(get_app_name). - It calls
get_app_name, gets"MyApp". - Calls
get_greeting("MyApp"), gets the greeting string. - Calls
greet(message=that_string).
You never manually wire them together. You only declare the dependencies and FastAPI resolves the graph.
Typical Use Cases in Backends
Dependency injection is especially useful for:
- Database sessions.
- Authentication and authorization.
- Configuration.
- Reusable logic like pagination or rate limiting.
You will see examples of each pattern in other chapters, but here we focus on the DI technique itself.
Request-Scoped Dependencies and `yield`
Many backend resources must be created at the start of a request and cleaned up at the end, such as:
- Database connections.
- External service clients.
- File handles.
FastAPI supports this using a dependency function that uses yield instead of return.
Pattern:
from fastapi import Depends
def get_resource():
# setup
resource = create_resource()
try:
yield resource
finally:
# cleanup
resource.close()FastAPI:
- Runs the code before
yieldwhen the dependency is first used. - Gives the yielded value to the dependent function.
- After the request is finished, runs the
finallyblock to clean up.
A typical database session dependency (simplified):
from fastapi import Depends
from sqlalchemy.orm import Session
def get_db() -> Session:
db = SessionLocal() # create session
try:
yield db # use it in routes
finally:
db.close() # always closeUsage in a route:
@app.get("/items")
def list_items(db: Session = Depends(get_db)):
return db.query(Item).all()
The route does not know how the session is created or closed. It only receives a usable db object.
Important rule
Use yield in dependencies when you must guarantee cleanup after the request.
Do not manually close or dispose such resources in your route handlers.
Reusing Dependencies Across Routes
One of the main benefits of DI is reusability. You can attach the same dependency to many routes.
Example: a simple authentication check dependency.
from fastapi import Depends, HTTPException, status
def get_token_header(x_token: str | None = None):
if x_token != "secret-token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid X-Token header",
)
@app.get("/secure-data", dependencies=[Depends(get_token_header)])
def secure_data():
return {"data": "Top secret"}
Here, get_token_header:
- Does not return anything.
- Raises an exception if the condition fails.
- Is applied to the route through
dependencies=[Depends(get_token_header)].
You can attach Depends(get_token_header) to as many routes as you want.
Two main ways to use a dependency:
| Pattern | When to use |
|---|---|
param: Type = Depends(func) | You need the return value of the dependency in the function. |
dependencies=[Depends(func)] | You only need side effects or validation, not the value. |
Class-Based Dependencies
Dependencies do not have to be plain functions. They can also be classes that define an __call__ method, or classes that are used to hold parameters and logic.
Simple class dependency
Example of a dependency that uses a class instance:
from fastapi import FastAPI, Depends
app = FastAPI()
class Calculator:
def __init__(self, base: int):
self.base = base
def double(self) -> int:
return self.base * 2
def get_calculator() -> Calculator:
return Calculator(base=10)
@app.get("/calc")
def calc_value(calc: Calculator = Depends(get_calculator)):
return {"double": calc.double()}Here:
get_calculatoris the dependency function.- The route receives a
Calculatorinstance.
Using class as the dependency itself
You can also use a class directly as a dependency. FastAPI will instantiate it, injecting other dependencies into its parameters.
from fastapi import Depends
class CommonQueryParams:
def __init__(
self,
q: str | None = None,
page: int = 1,
limit: int = 10,
):
self.q = q
self.page = page
self.limit = limit
@app.get("/items")
def list_items(params: CommonQueryParams = Depends()):
return {
"query": params.q,
"page": params.page,
"limit": params.limit,
}Explanation:
Depends()without arguments means: “use the type itself as the dependency,” hereCommonQueryParams.- FastAPI will inspect
__init__, readq,page,limitand map them to query parameters.
You can reuse CommonQueryParams in many routes.
Another variant with __call__:
class Multiplier:
def __init__(self, factor: int = 2):
self.factor = factor
def __call__(self, value: int) -> int:
return value * self.factor
@app.get("/multiply/{value}")
def multiply(
result: int = Depends(Multiplier) # Multiplier is used as dependency
):
return {"result": result}FastAPI:
- Creates
Multiplier(factor=2). - Sees
result: int = Depends(Multiplier). - Calls the created instance as a function with parameters from the request, here
value. - Injects the output into
result.
Dependency Injection in Path Operations
The most common place to use dependencies is in your path operations.
from fastapi import FastAPI, Depends
app = FastAPI()
def get_current_user():
# Here you would normally read a token, load from DB, etc.
return {"username": "alice"}
@app.get("/me")
def read_me(current_user: dict = Depends(get_current_user)):
return current_userTypical patterns:
- Inject a current user object from a token.
- Inject a database session.
- Inject pagination parameters.
- Inject configuration.
Multiple dependencies in one route:
from sqlalchemy.orm import Session
def get_db() -> Session:
db = SessionLocal()
try:
yield db
finally:
db.close()
def get_current_user():
return {"username": "alice"}
@app.get("/posts")
def list_posts(
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
posts = db.query(Post).filter(Post.owner == current_user["username"]).all()
return posts
FastAPI will resolve all dependencies before calling list_posts:
- Create and yield
db. - Get
current_user. - Call the route handler.
Global and Router-Level Dependencies
Sometimes you want a dependency to run for many routes at once, like a global authentication check or a logging hook.
You can attach dependencies to:
- The whole app.
- A router.
- A single route.
App-level dependencies
from fastapi import FastAPI, Depends, Request
app = FastAPI()
def log_request(request: Request):
print("Incoming:", request.method, request.url)
app = FastAPI(dependencies=[Depends(log_request)])
@app.get("/one")
def route_one():
return {"route": "one"}
@app.get("/two")
def route_two():
return {"route": "two"}
Here log_request runs for every request, but its result is not injected as a parameter anywhere.
Router-level dependencies
from fastapi import APIRouter
router = APIRouter(
prefix="/admin",
dependencies=[Depends(get_token_header)], # will apply to all routes here
)
@router.get("/users")
def list_admin_users():
return [{"username": "admin1"}]
@router.get("/stats")
def admin_stats():
return {"users": 42}
app.include_router(router)get_token_headerruns for all routes inside/admin.- Routes do not receive any parameter from it, because it is used for validation only.
Dependency Injection and Testing
Dependency injection makes testing much easier because you can override dependencies with test versions.
FastAPI exposes app.dependency_overrides to do this.
Suppose you have:
def get_db() -> Session:
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/items")
def list_items(db: Session = Depends(get_db)):
return db.query(Item).all()
In tests you can override get_db:
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
client = TestClient(app)
def override_get_db() -> Session:
# use a test database or a fake object
db = TestSessionLocal()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
def test_list_items():
response = client.get("/items")
assert response.status_code == 200During tests:
- Whenever FastAPI needs
get_db, it callsoverride_get_dbinstead. - Your application code does not change.
You can override any dependency in this way, for example get_current_user to simulate authenticated users.
Optional Dependencies
Sometimes a dependency is not mandatory. You can allow it to fail without raising an error by using Depends with use_cache and default or by catching exceptions inside the dependency.
A common pattern is to make a dependency return None if authentication is missing:
from fastapi import Depends, Header, HTTPException
def get_optional_token(x_token: str | None = Header(default=None)):
if x_token is None:
return None
if x_token != "secret-token":
raise HTTPException(status_code=403, detail="Invalid token")
return x_token
@app.get("/maybe-secure")
def maybe_secure(x_token: str | None = Depends(get_optional_token)):
if x_token is None:
return {"message": "Public access"}
return {"message": "Private access", "token": x_token}
The route can then behave differently depending on whether the dependency returned something or None.
Caching Dependency Results per Request
By default, FastAPI calls each dependency once per request for each unique combination of parameters. If multiple parameters or dependencies refer to the same dependency with the same arguments, FastAPI reuses the result.
Example:
def get_config():
print("Config dependency called")
return {"debug": True}
@app.get("/a")
def route_a(cfg: dict = Depends(get_config)):
return cfg
@app.get("/b")
def route_b(
cfg1: dict = Depends(get_config),
cfg2: dict = Depends(get_config),
):
return {"same_object": cfg1 is cfg2}
For /b in a single request:
get_configis called once.cfg1andcfg2receive the same object.
You can disable this behavior in rare cases by setting use_cache=False:
@app.get("/c")
def route_c(
cfg1: dict = Depends(get_config, use_cache=False),
cfg2: dict = Depends(get_config, use_cache=False),
):
return {"same_object": cfg1 is cfg2} # will be FalseIn most backend use cases, keep the default caching because it saves work and keeps consistency inside a single request.
Important rule
Within a single request, a dependency is called at most once for a given parameter combination, unless you set use_cache=False.
This makes dependencies predictable and efficient.
Common Patterns and Examples
To make the concepts concrete, here are a few patterns that you will frequently use.
Database session per request
from fastapi import Depends
from sqlalchemy.orm import Session
def get_db() -> Session:
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users")
def create_user(user_in: UserCreate, db: Session = Depends(get_db)):
user = User(**user_in.model_dump())
db.add(user)
db.commit()
db.refresh(user)
return userCurrent user from token
from fastapi import Depends, HTTPException, status, Header
def get_current_user(authorization: str | None = Header(default=None)):
if authorization is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing Authorization header",
)
scheme, _, token = authorization.partition(" ")
if scheme.lower() != "bearer" or token != "valid-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
)
return {"username": "alice"}
@app.get("/profile")
def read_profile(current_user: dict = Depends(get_current_user)):
return current_userCommon pagination parameters
from fastapi import Query, Depends
class Pagination:
def __init__(
self,
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=100),
):
self.page = page
self.size = size
@app.get("/products")
def list_products(p: Pagination = Depends()):
skip = (p.page - 1) * p.size
limit = p.size
return {"page": p.page, "size": p.size, "skip": skip, "limit": limit}Summary
Dependency injection in FastAPI:
- Uses
Dependsto describe what your functions need. - Lets FastAPI create, resolve, and clean up those needs automatically.
- Encourages separation of concerns, making code cleaner and more testable.
- Supports functions, classes, and
yieldfor setup and teardown. - Can be attached at route, router, or app level, and easily overridden in tests.
You will rely on this system heavily when adding authentication, database access, configuration, and more to your FastAPI backends.
Views: 9
KAHIBARO