Skip to main content

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:

python
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:

python
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.

python
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:

python
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:

  1. Isolation: Request state is isolated to each request, preventing data leakage between concurrent requests.
  2. Lifecycle management: The state is automatically cleaned up when the request ends.
  3. Concurrency safety: You don't have to worry about race conditions when handling multiple requests.

Consider this example showing the difference:

python
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

  1. Create a middleware that adds a unique request ID to each request's state and then returns it as a response header.
  2. Implement a request state-based caching system that stores database query results during a request.
  3. Build an error tracking system that uses request state to maintain context about the request for detailed error logging.
  4. 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! :)