FastAPI Request State
When building web applications with FastAPI, you'll often need to share data across different parts of your request processing pipeline. FastAPI provides a convenient built-in mechanism called request state that allows you to store and access data throughout the lifecycle of a request. In this tutorial, you'll learn how to use request state effectively in your FastAPI applications.
What is Request State?
Request state is a property of the FastAPI Request
object that acts as a storage location for arbitrary data that needs to be accessible throughout the request's lifecycle. It's essentially a dictionary-like object where you can store and retrieve values using attribute access syntax.
The request state is particularly useful for:
- Storing authentication information
- Sharing database connections
- Passing data between middleware and endpoints
- Maintaining context across dependency functions
How to Access Request State
To use request state in FastAPI, you first need to import the Request
object from FastAPI and include it as a parameter in your path operation function:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/")
async def read_root(request: Request):
# Access the request state
request.state.user_id = 123
return {"message": "Request state set successfully"}
Once you have the Request
object, you can access its state
attribute and assign or read values as needed.
Using Request State in Middleware
One of the most common use cases for request state is to store data in middleware that you want to access later in your route handlers.
Let's create a simple middleware that adds a timestamp to the request state:
from fastapi import FastAPI, Request
import time
app = FastAPI()
@app.middleware("http")
async def add_timestamp_middleware(request: Request, call_next):
# Store the current timestamp in request state
request.state.timestamp = time.time()
request.state.processing_start = time.time()
# Process the request
response = await call_next(request)
# Calculate processing time
request.state.processing_time = time.time() - request.state.processing_start
# You could add the processing time to response headers
response.headers["X-Process-Time"] = str(request.state.processing_time)
return response
@app.get("/")
async def read_root(request: Request):
return {
"timestamp": request.state.timestamp,
"processing_time_so_far": time.time() - request.state.processing_start
}
In this example, the middleware adds a timestamp to the request state before the request handler is called. The request handler can then access this timestamp.
Sharing Data Between Dependencies
Request state is very useful when you need to share data between dependency functions without having to pass it explicitly through function parameters.
from fastapi import FastAPI, Request, Depends
app = FastAPI()
async def get_db_connection(request: Request):
# Simulate opening a database connection
db_connection = {"connection_id": "db123"}
# Store the connection in request state
request.state.db_connection = db_connection
return db_connection
async def get_current_user(request: Request):
# Simulate user authentication
user = {"id": 1, "name": "John Doe"}
# Store the user in request state
request.state.user = user
return user
@app.get("/items/")
async def read_items(
request: Request,
db: dict = Depends(get_db_connection),
user: dict = Depends(get_current_user)
):
# You can access both the db and user either from the dependencies
# or from the request state
return {
"user": request.state.user,
"db_connection_id": request.state.db_connection["connection_id"],
"items": ["Item1", "Item2"]
}
This approach can help keep your code cleaner by avoiding the need to pass the same objects through long chains of function calls.
Real-World Example: Authentication Context
Here's a more practical example showing how request state can be used to maintain authentication context throughout a request:
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Mock database
fake_users_db = {
"johndoe": {
"id": 1,
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
}
}
async def get_current_user(request: Request, token: str = Depends(oauth2_scheme)):
# Normally, you'd validate the token here
# For simplicity, we'll just assume the token is the username
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
# Store the user in request state for easy access elsewhere
request.state.user = user
return user
@app.get("/users/me")
async def read_users_me(request: Request, user: dict = Depends(get_current_user)):
return {"user_info": request.state.user}
@app.get("/items/protected")
async def read_protected_items(request: Request, user: dict = Depends(get_current_user)):
return {
"items": ["Protected Item 1", "Protected Item 2"],
"owner": request.state.user["username"]
}
In this example, the authentication dependency get_current_user
validates the token and stores the user information in the request state. Any route that depends on get_current_user
can then easily access this information.
Request State vs. Global Variables
You might wonder why you should use request state instead of global variables. There are several important reasons:
- Isolation: Request state is isolated to each request, preventing data leakage between concurrent requests.
- Lifecycle management: The state is automatically cleaned up when the request ends.
- Concurrency safety: You don't have to worry about race conditions when handling multiple requests.
Consider this example showing the difference:
from fastapi import FastAPI, Request
import uuid
app = FastAPI()
# INCORRECT: Global variable (shared across all requests)
global_request_id = None
@app.middleware("http")
async def request_middleware(request: Request, call_next):
# Using global variable (BAD PRACTICE)
global global_request_id
global_request_id = str(uuid.uuid4())
# Using request state (GOOD PRACTICE)
request.state.request_id = str(uuid.uuid4())
response = await call_next(request)
return response
@app.get("/request-info")
async def get_request_info(request: Request):
return {
"global_id": global_request_id, # Problematic with concurrent requests!
"request_state_id": request.state.request_id # Correctly isolated per request
}
If two requests come in simultaneously, the global variable will be overwritten and lead to incorrect behavior, while the request state will maintain separate values for each request.
Summary
FastAPI's request state provides a convenient way to store and share data throughout the request lifecycle. Key benefits include:
- Sharing data between middleware and route handlers
- Maintaining context across dependency functions
- Avoiding the need to pass objects through multiple function calls
- Isolating data between concurrent requests
- Automatically cleaning up when the request ends
By using request state effectively, you can write cleaner, more maintainable FastAPI applications while ensuring proper isolation between requests.
Additional Resources
Exercises
- Create a middleware that adds a unique request ID to each request's state and then returns it as a response header.
- Implement a request state-based caching system that stores database query results during a request.
- Build an error tracking system that uses request state to maintain context about the request for detailed error logging.
- Develop a permission system that stores user roles and permissions in the request state after authentication for easy access in route handlers.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)