FastAPI processes requests through a specific execution pipeline: middleware runs first (outermost to innermost), then dependencies (in declaration order), then the route handler. The response flows back through the same layers in reverse.
Request/Response Flow:
INBOUND REQUEST:
1. Middleware Layer 1 (first added) - before call_next()
2. Middleware Layer 2 (second added) - before call_next()
3. Middleware Layer N (last added) - before call_next()
4. Route Dependencies (in declaration order)
5. Path Operation Function (route handler)
OUTBOUND RESPONSE:
6. Middleware Layer N (last added) - after call_next()
7. Middleware Layer 2 (second added) - after call_next()
8. Middleware Layer 1 (first added) - after call_next()
9. Return to client
Detailed Example:
from fastapi import Depends, FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
import time
app = FastAPI()
# MIDDLEWARE LAYER 1 (added first, executes OUTERMOST)
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
print("1. TimingMiddleware - BEFORE call_next")
start = time.time()
response = await call_next(request) # Passes to next layer
duration = time.time() - start
print(f"8. TimingMiddleware - AFTER call_next ({duration:.3f}s)")
response.headers["X-Process-Time"] = str(duration)
return response
# MIDDLEWARE LAYER 2 (added second, executes INSIDE Layer 1)
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
print("2. AuthMiddleware - BEFORE call_next")
token = request.headers.get("Authorization")
request.state.token = token
response = await call_next(request) # Passes to next layer
print("7. AuthMiddleware - AFTER call_next")
return response
# MIDDLEWARE LAYER 3 (added third, executes INSIDE Layer 2)
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
print(f"3. LoggingMiddleware - BEFORE call_next - {request.url.path}")
response = await call_next(request) # Passes to dependencies
print(f"6. LoggingMiddleware - AFTER call_next - {response.status_code}")
return response
app.add_middleware(TimingMiddleware) # Outermost
app.add_middleware(AuthMiddleware) # Middle
app.add_middleware(LoggingMiddleware) # Innermost
# DEPENDENCY LAYER (executes after all middleware)
async def verify_token(request: Request):
print("4. Dependency: verify_token")
token = request.state.token
if not token:
raise HTTPException(status_code=401, detail="No token")
return {"user_id": 123}
async def get_db():
print("4. Dependency: get_db")
db = {"connection": "active"}
try:
yield db
finally:
print("5. Dependency cleanup: get_db")
# ROUTE HANDLER (executes last)
@app.get("/users")
async def get_users(
user: dict = Depends(verify_token), # Executes first
db: dict = Depends(get_db) # Executes second
):
print("5. Route Handler: get_users")
return {"users": [], "auth": user}
Console Output for GET /users:
1. TimingMiddleware - BEFORE call_next
2. AuthMiddleware - BEFORE call_next
3. LoggingMiddleware - BEFORE call_next - /users
4. Dependency: verify_token
4. Dependency: get_db
5. Route Handler: get_users
5. Dependency cleanup: get_db
6. LoggingMiddleware - AFTER call_next - 200
7. AuthMiddleware - AFTER call_next
8. TimingMiddleware - AFTER call_next (0.003s)
Key Principles:
- Middleware Stack (LIFO for response): Last middleware added executes closest to dependencies
- Dependencies Execute in Order: Listed left-to-right in function signature
- Cleanup in Reverse: Generator dependencies cleanup after route handler
- Early Exit: Middleware can return response before
call_next(), skipping remaining pipeline - State Passing: Middleware can set
request.state.*for dependencies to access
Early Exit Example:
class CacheMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
cached = get_from_cache(request.url.path)
if cached:
# Skip dependencies and route handler entirely
return JSONResponse(content=cached)
response = await call_next(request)
save_to_cache(request.url.path, response)
return response
Middleware vs Dependencies Decision Guide:
- Use Middleware For: Request/response transformation, timing, logging, early exits, global behavior
- Use Dependencies For: Authentication (route-specific), database connections, business logic, dependency injection, testability
Version Note: Execution order consistent since FastAPI 0.40.0+, Starlette 0.13.0+