When building FastAPI applications, you'll often need to share data across different parts of your request lifecycle i.e. middleware, dependencies, and route handlers. FastAPI provides two primary mechanisms for this: request.state and context variables (contextvars). But when should you use each one?
Understanding request.state π¦
request.state is a simple namespace object attached to each FastAPI request. It's designed to store arbitrary data that needs to be shared during the processing of a single request.
Basic Usage π§
from fastapi import FastAPI, Request, Depends app = FastAPI() @app.middleware("http") async def add_user_info(request: Request, call_next): # Store data in request.state request.state.user_id = "user_123" request.state.request_time = time.time() response = await call_next(request) return response @app.get("/profile") async def get_profile(request: Request): # Access data from request.state user_id = request.state.user_id return {"user_id": user_id, "message": "User profile"} Pros of request.state β
- β Simple and straightforward
- β Explicitly tied to the request object
- β Easy to understand and debug
- β No additional imports needed
- β Works well with dependency injection
Cons of request.state β
- β Requires passing the
Requestobject around - β Not accessible in deeply nested functions without explicit passing
- β Can become verbose in complex applications
Understanding Context Variables π
Context variables (contextvars) provide a way to store data that's automatically available throughout the execution context of a request, without explicitly passing it around.
Basic Usage π
from contextvars import ContextVar from fastapi import FastAPI, Request import time # Define context variables user_id_var: ContextVar[str] = ContextVar('user_id') request_time_var: ContextVar[float] = ContextVar('request_time') app = FastAPI() @app.middleware("http") async def add_user_info(request: Request, call_next): # Set context variables user_id_var.set("user_123") request_time_var.set(time.time()) response = await call_next(request) return response @app.get("/profile") async def get_profile(): # Access context variables from anywhere user_id = user_id_var.get() request_time = request_time_var.get() return { "user_id": user_id, "request_time": request_time, "message": "User profile" } Pros of Context Variables β
- β Available anywhere in the execution context
- β No need to pass request object around
- β Cleaner function signatures
- β Automatic cleanup after request completion
- β Thread-safe and async-safe
Cons of Context Variables β
- β Less explicit than request.state
- β Harder to debug and trace
- β Requires additional setup and imports
- β Can make testing more complex
Advanced Patterns π¨
Using Context Variables with Dependencies π
from contextvars import ContextVar from fastapi import FastAPI, Depends, HTTPException from typing import Optional current_user_var: ContextVar[Optional[dict]] = ContextVar('current_user', default=None) app = FastAPI() async def get_current_user() -> dict: user = current_user_var.get() if not user: raise HTTPException(status_code=401, detail="User not authenticated") return user @app.middleware("http") async def auth_middleware(request: Request, call_next): # Simulate user authentication token = request.headers.get("authorization") if token: user = {"id": "123", "name": "John Doe"} # Simulate user lookup current_user_var.set(user) response = await call_next(request) return response @app.get("/protected") async def protected_route(user: dict = Depends(get_current_user)): return {"message": f"Hello {user['name']}!"} Combining Both Approaches π€
from contextvars import ContextVar from fastapi import FastAPI, Request import uuid # Context var for request ID (global access) request_id_var: ContextVar[str] = ContextVar('request_id') app = FastAPI() @app.middleware("http") async def request_middleware(request: Request, call_next): # Generate request ID and store in both places request_id = str(uuid.uuid4()) # Store in context var for global access request_id_var.set(request_id) # Store in request.state for explicit access request.state.request_id = request_id request.state.start_time = time.time() response = await call_next(request) return response Performance Considerations β‘
Memory Usage
- request.state: Minimal overhead, data is garbage collected with the request ποΈ
- contextvars: Slightly higher overhead due to context management, but still very efficient πͺ
When to Use What? π€
Use request.state when:
- π― You want explicit, traceable data flow
- π― Building simple applications
- π― You're already passing Request objects around
- π― You need to store request-specific metadata
- π― Debugging and testing simplicity is important
Use context variables when:
- π― You have deeply nested function calls
- π― You want cleaner function signatures
- π― Building complex applications with many layers
- π― You need global access to request-scoped data
- π― Working with third-party libraries that need access to request data
Real-World Example: Logging System π
Here's how you might implement a request logging system with both approaches:
With Context Variables π
from contextvars import ContextVar import logging import uuid request_id_var: ContextVar[str] = ContextVar('request_id') logger = logging.getLogger(__name__) class RequestLogger: @staticmethod def info(message: str): request_id = request_id_var.get("unknown") logger.info(f"[{request_id}] {message}") @app.middleware("http") async def logging_middleware(request: Request, call_next): request_id_var.set(str(uuid.uuid4())) RequestLogger.info(f"Request started: {request.method} {request.url}") response = await call_next(request) RequestLogger.info(f"Request completed: {response.status_code}") return response @app.get("/users/{user_id}") async def get_user(user_id: int): RequestLogger.info(f"Fetching user {user_id}") # Your business logic here return {"user_id": user_id} With request.state π
import logging import uuid logger = logging.getLogger(__name__) def log_with_request_id(request: Request, message: str): request_id = getattr(request.state, 'request_id', 'unknown') logger.info(f"[{request_id}] {message}") @app.get("/users/{user_id}") async def get_user(user_id: int, request: Request): log_with_request_id(request, f"Fetching user {user_id}") # Your business logic here return {"user_id": user_id} Have you used both approaches in your FastAPI projects? Share your experiences in the comments below! π

Top comments (0)