Skip to main content
🛡️ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

FastAPI Dependency Injection: The Complete Guide from Basics to Advanced Patterns

· 9 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

Dependency Injection (DI) is one of the core architectural design choices that makes FastAPI so powerful. It handles cross-cutting concerns-such as database connections, security, authentication, and request parsing-with minimal boilerplate.

Whether you are a beginner looking to understand the Depends syntax or an expert building complex authorization layers and mockable tests, this guide covers everything you need to master FastAPI's dependency injection system.


Dependency Injection vs. Depends

While they are often used interchangeably, Dependency Injection (DI) and Depends are two different but closely related concepts:

  • Dependency Injection (DI): The fundamental design pattern or system FastAPI uses. It is an architectural concept where components (dependencies) are "injected" into route functions rather than being created inside them. This achieves Inversion of Control (IoC), decoupling your business logic from environment setup.
  • Depends(...): The special helper function provided by FastAPI. It acts as a syntax marker or signal that explicitly tells the framework's DI system which component needs to be resolved and injected into the function argument.

The Assembly Line Analogy

To visualize this relationship:

  • Dependency Injection (DI) is the assembly line itself. It represents the infrastructure that moves parts, runs checks, and hooks components together.
  • Depends(...) is the work order ticket attached to a specific spot on the car frame. It tells the assembly line exactly which part to fetch and install at that specific spot (e.g., "Install Engine Model 2000").

Quickstart: Functions as Dependencies

The simplest dependency is a standard Python function. When a route is called, FastAPI executes the dependency function first and passes its return value to your endpoint.

Example: Pagination Dependency

from fastapi import FastAPI, Depends
from typing import Annotated

app = FastAPI()

# 1. Define the dependency function
def get_pagination_params(skip: int = 0, limit: int = 100):
return {"skip": skip, "limit": limit}

# Define a reusable type hint using Annotated
Pagination = Annotated[dict, Depends(get_pagination_params)]

# 2. Inject it into your route handler
@app.get("/items")
def read_items(pagination: Pagination):
return {"skip": pagination["skip"], "limit": pagination["limit"]}

Advanced Dependency Injection Patterns

1. Yield and Teardown Lifecycles (Database Sessions)

For resources that require teardown after a request completes-such as database sessions, file locks, or network sockets-use a generator function with the yield keyword instead of return (2).

FastAPI runs the code up to the yield statement before executing the route, injects the yielded object, and then runs the code after the yield (even if the route raises an HTTP exception).

from typing import Generator

class DBSession:
def commit(self):
pass
def rollback(self):
pass
def close(self):
pass

def get_db() -> Generator[DBSession, None, None]:
db = DBSession()
try:
yield db # Injected into the route
db.commit() # Runs after route completes successfully
except Exception:
db.rollback() # Runs if route raises an exception
raise
finally:
db.close() # Guaranteed to run, cleaning up resources

2. Class-Based Dependencies

Instead of functions, you can use classes as dependencies. This is useful for parameterizing dependencies or building services that maintain internal configuration.

class QueryHeaderChecker:
def __init__(self, required_header: str):
self.required_header = required_header

def __call__(self, x_token: str = Header(...)):
if x_token != self.required_header:
raise HTTPException(status_code=400, detail="Invalid Token")
return x_token

# Initialize the dependency with configuration
token_verifier = QueryHeaderChecker(required_header="secret-token-value")

@app.get("/secure-data")
def get_secure_data(token: str = Depends(token_verifier)):
return {"message": "Success"}

3. Nested Sub-Dependencies and Role-Based Authorization

Dependencies can depend on other dependencies. This allows you to chain logic, such as performing authentication first, and then using the resolved user to check roles.

class User:
def __init__(self, username: str, roles: list[str]):
self.username = username
self.roles = roles

def get_current_user() -> User:
# Authenticate via JWT or Session
return User(username="alice", roles=["manager", "admin"])

def role_required(required_role: str):
# Dependency Factory
def check_role(user: User = Depends(get_current_user)):
if required_role not in user.roles:
raise HTTPException(status_code=403, detail="Forbidden")
return user
return check_role

@app.get("/admin/panel")
def admin_panel(user: User = Depends(role_required("admin"))):
return {"message": f"Welcome Admin {user.username}"}

Dependency Lifetime, Caching, and Singletons

By default, if you use the same dependency multiple times in a single request (e.g., in a sub-dependency chain and your main route), FastAPI will run the dependency once and cache the result for the duration of that request. You can disable this behavior using use_cache=False:

# Force execution every time it is referenced in the request
data = Depends(get_data, use_cache=False)

Enforcing Global Singletons

While FastAPI caches dependencies request-by-request, it does not guarantee that underlying classes are singletons across the entire application lifecycle. To build a service initialized only once (like config managers or machine learning models), override __new__:

import time

class GlobalConfigService:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super(GlobalConfigService, cls).__new__(cls)
# Costly setup runs once
cls._instance.version = "v1.0.0"
cls._instance.start_time = time.time()
return cls._instance

def __call__(self):
return self

Path, Router, and Global Dependencies

Sometimes you want to run a dependency for an entire router or path operation, but you don't need its return value injected into your route function argument. You can declare these directly in the path decorator or the APIRouter configuration (3):

from fastapi import APIRouter

# Applies role_required("admin") validation to all routes inside this router
admin_router = APIRouter(
prefix="/admin",
dependencies=[Depends(role_required("admin"))]
)

@admin_router.get("/dashboard")
def dashboard():
# Only executes if role verification passes
return {"status": "ok"}

Testing with Dependency Overrides

A major benefit of Dependency Injection is testability. In your unit tests, you can mock databases or authentication layers by patching the app.dependency_overrides dictionary:

from fastapi.testclient import TestClient
import pytest

client = TestClient(app)

# Mock dependency
def get_mock_db():
return {"data": "mock-db-contents"}

def test_read_items():
# 1. Apply the override
app.dependency_overrides[get_db] = get_mock_db

# 2. Execute test request
response = client.get("/items")
assert response.status_code == 200
assert response.json() == {"data": "mock-db-contents"}

# 3. Clean up the override
app.dependency_overrides.clear()

Sources

Related articles