Difficulty: Intermediate
While most dependencies are declared on individual endpoints, FastAPI also supports dependencies at the application level and router level. These global dependencies run for every request that matches the scope, making them perfect for cross-cutting concerns like authentication, logging, and rate limiting.
Application-level dependencies are set when creating the FastAPI instance: app = FastAPI(dependencies=[Depends(verify_token)]). Every endpoint in the application will require this dependency. If the dependency raises an exception, the request is rejected before any handler runs.
Router-level dependencies are set when creating an APIRouter: router = APIRouter(dependencies=[Depends(verify_token)]). These apply to all endpoints in that specific router. This is useful when certain groups of endpoints need authentication but others (like health checks) do not.
Dependency overrides are a powerful testing feature. Using app.dependency_overrides, you can replace a dependency with a mock for testing. For example, replacing the database session dependency with a test database session, or replacing an authentication dependency with a function that always returns a test user.
The pattern for overriding is: app.dependency_overrides[original_dependency] = mock_dependency. The mock function must have the same return type as the original. After testing, clear the overrides with app.dependency_overrides.clear().
Global dependencies vs middleware: both run on every request, but they serve different purposes. Dependencies have access to FastAPI's parameter resolution (they can declare Query, Header, etc.) and their results can be injected into handlers. Middleware has access to the raw request and response and can modify both. Use dependencies for request validation and data extraction; use middleware for cross-cutting processing like logging and CORS.
from fastapi import FastAPI, Depends, HTTPException, Header, APIRouter
# Dependency that runs on every request
def verify_api_version(x_api_version: str = Header(default="1")):
if x_api_version not in ["1", "2"]:
raise HTTPException(status_code=400, detail="Unsupported API version")
return x_api_version
# Dependency for authenticated routes
def require_auth(authorization: str = Header()):
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Not authenticated")
return authorization.split(" ")[1]
# App-level dependency (runs for ALL endpoints)
app = FastAPI(dependencies=[Depends(verify_api_version)])
# Public router (no auth required)
public_router = APIRouter(prefix="/public", tags=["Public"])
@public_router.get("/health")
def health_check():
return {"status": "ok"}
# Protected router (auth required for ALL endpoints in this router)
protected_router = APIRouter(
prefix="/api",
tags=["Protected"],
dependencies=[Depends(require_auth)],
)
@protected_router.get("/profile")
def get_profile():
return {"user": "Alice"}
@protected_router.get("/settings")
def get_settings():
return {"theme": "dark"}
app.include_router(public_router)
app.include_router(protected_router)
# /public/health - needs X-API-Version header only
# /api/profile - needs X-API-Version AND Authorization headers
# /api/settings - needs X-API-Version AND Authorization headers
verify_api_version runs for every request (app level). require_auth runs only for endpoints in the protected router. /public/health only needs the API version header, while /api/* endpoints need both.
from fastapi import FastAPI, Depends, HTTPException
from fastapi.testclient import TestClient
app = FastAPI()
# Production dependency
def get_current_user():
# In production, this reads from a database
raise HTTPException(status_code=401, detail="Not authenticated")
# Production dependency
def get_db():
return {"type": "production", "host": "prod-db.example.com"}
@app.get("/profile")
def read_profile(user: dict = Depends(get_current_user)):
return user
@app.get("/data")
def read_data(db: dict = Depends(get_db)):
return {"db": db, "data": [1, 2, 3]}
# --- Testing ---
# Mock dependencies
def mock_current_user():
return {"id": 1, "name": "Test User", "role": "admin"}
def mock_db():
return {"type": "test", "host": "localhost"}
# Override dependencies
app.dependency_overrides[get_current_user] = mock_current_user
app.dependency_overrides[get_db] = mock_db
client = TestClient(app)
response = client.get("/profile")
# Returns {"id": 1, "name": "Test User", "role": "admin"}
# No 401 error because mock skips authentication
# Clean up
app.dependency_overrides.clear()
dependency_overrides replaces dependencies during testing. The mock_current_user returns a test user without authentication. The mock_db uses a test database. After testing, clear() restores the original dependencies.
from fastapi import FastAPI, Depends, HTTPException, Request
import time
# Rate limiting dependency
request_counts: dict[str, list[float]] = {}
def rate_limit(request: Request):
client_ip = request.client.host
now = time.time()
window = 60 # 1 minute window
max_requests = 100
if client_ip not in request_counts:
request_counts[client_ip] = []
# Remove old timestamps
request_counts[client_ip] = [
ts for ts in request_counts[client_ip]
if now - ts < window
]
if len(request_counts[client_ip]) >= max_requests:
raise HTTPException(
status_code=429,
detail="Too many requests",
headers={"Retry-After": "60"},
)
request_counts[client_ip].append(now)
app = FastAPI(dependencies=[Depends(rate_limit)])
@app.get("/")
def root():
return {"message": "Hello"}
# Every endpoint is automatically rate-limited
# No need to add the dependency to each handler
The rate_limit dependency runs for every request as an app-level dependency. It tracks request counts per IP and returns 429 when the limit is exceeded. All endpoints are protected without any per-endpoint configuration.
App Dependencies, Router Dependencies, Dependency Override, Testing, Middleware vs Dependencies