Middleware¶
Django-Bolt provides middleware for cross-cutting concerns like CORS and rate limiting. This guide covers the built-in middleware and how to use it.
CORS middleware¶
Per-route CORS¶
Apply CORS to specific endpoints:
from django_bolt.middleware import cors
@api.get("/api/data")
@cors(origins=["https://example.com"], credentials=True)
async def get_data():
return {"data": "value"}
CORS options¶
@cors(
origins=["https://example.com", "https://app.example.com"],
methods=["GET", "POST", "PUT", "DELETE"],
headers=["Content-Type", "Authorization"],
credentials=True,
max_age=3600, # Preflight cache duration
)
Global CORS¶
Configure CORS for all endpoints in settings.py:
# Allow specific origins
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://app.example.com",
]
# Allow all origins (development only!)
CORS_ALLOW_ALL_ORIGINS = True
# Additional settings
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
CORS_ALLOW_HEADERS = ["Content-Type", "Authorization", "X-Requested-With"]
CORS_EXPOSE_HEADERS = ["X-Total-Count", "X-Page-Count"]
CORS_MAX_AGE = 86400 # 24 hours
Rate limiting¶
Per-route rate limiting¶
from django_bolt.middleware import rate_limit
@api.get("/api/search")
@rate_limit(rps=10, burst=20)
async def search(q: str):
return {"results": []}
Parameters:
rps- Requests per second allowedburst- Maximum burst size (allows short spikes)
How it works¶
Django-Bolt uses a token bucket algorithm:
- Tokens are added at
rpsper second - Each request consumes one token
- The bucket holds up to
bursttokens - If no tokens are available, the request is rejected with 429 Too Many Requests
Rate limit response¶
When rate limited, the response includes:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000000
Skipping middleware¶
Disable specific middleware for an endpoint:
from django_bolt.middleware import skip_middleware
@api.get("/health")
@skip_middleware("cors", "rate_limit")
async def health():
return {"status": "ok"}
Or skip all middleware:
Compression¶
Django-Bolt automatically compresses responses. Disable for specific endpoints:
from django_bolt.middleware import no_compress
@api.get("/stream")
@no_compress
async def stream():
# Streaming responses should not be compressed
return StreamingResponse(generate())
Compression settings¶
Configure in settings.py:
from django_bolt import CompressionConfig
# In your api.py
api = BoltAPI(
compression=CompressionConfig(
backend="brotli", # "brotli", "gzip", or "zstd"
minimum_size=1000, # Only compress responses > 1KB
gzip_fallback=True, # Fall back to gzip if client doesn't support brotli
)
)
Custom middleware¶
Function-style middleware (per-route)¶
Use @middleware on a function that accepts (request, call_next):
from django_bolt.middleware import middleware
@middleware
async def timing_middleware(request, call_next):
request.state["timing_enabled"] = True
response = await call_next(request)
response.headers["X-Timing"] = "enabled"
return response
@api.get("/timed")
@timing_middleware
async def timed_endpoint():
return {"status": "ok"}
In this form, @middleware creates a route decorator and runs with the (request, call_next) contract.
Class-based middleware (global)¶
Define a Django-style middleware class and pass the class to BoltAPI(middleware=[...]):
from django_bolt import BoltAPI
from django_bolt.middleware import Middleware
import uuid
class RequestIdMiddleware(Middleware):
async def process_request(self, request):
request_id = str(uuid.uuid4())
request.state["request_id"] = request_id
response = await self.get_response(request)
response.headers["X-Request-ID"] = request_id
return response
api = BoltAPI(
middleware=[
RequestIdMiddleware, # pass class, not instance
]
)
Class-based middleware (per-route)¶
You can apply class-based middleware to a single route with @middleware(MyMiddlewareClass):
from django_bolt.middleware import middleware
@api.get("/admin-only")
@middleware(RequestIdMiddleware)
async def admin_only():
return {"ok": True}
Class-based middleware (router-level)¶
Router middleware applies to all routes in that router (including nested routers):
from django_bolt import Router
admin_router = Router(
prefix="/admin",
middleware=[
RequestIdMiddleware,
]
)
@admin_router.get("/users")
async def list_users():
return {"items": []}
api.include_router(admin_router)
Allowed middleware entries¶
- Middleware classes (
__init__(get_response)) DjangoMiddleware(...)wrappersDjangoMiddlewareStack(...)wrappers- Dict middleware configs for Rust-handled middleware (
cors,rate_limit)
Passing plain middleware instances in these lists fails fast with TypeError (pass class, not instance).
Router middleware is inherited parent-to-child and executes in declared order.
Django middleware integration¶
Django-Bolt seamlessly integrates with Django's middleware system, allowing you to use existing Django middleware with your API endpoints.
Quick start¶
The simplest approach is to use the django_middleware parameter, which loads middleware from your Django settings.MIDDLEWARE:
from django_bolt import BoltAPI
# Load all middleware from settings.MIDDLEWARE
api = BoltAPI(django_middleware=True)
Configuration options¶
The django_middleware parameter accepts several configuration formats:
# Load all middleware from settings.MIDDLEWARE
api = BoltAPI(django_middleware=True)
# Disable Django middleware
api = BoltAPI(django_middleware=False)
# Load specific middleware only
api = BoltAPI(django_middleware=[
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
])
# Exclude specific middleware
api = BoltAPI(django_middleware={
"exclude": ["django.middleware.csrf.CsrfViewMiddleware"]
})
# Include only specific middleware
api = BoltAPI(django_middleware={
"include": [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
]
})
Using DjangoMiddleware wrapper¶
For wrapping individual middleware classes directly:
from django_bolt import BoltAPI, DjangoMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth.middleware import AuthenticationMiddleware
api = BoltAPI(
middleware=[
DjangoMiddleware(SessionMiddleware),
DjangoMiddleware(AuthenticationMiddleware),
]
)
You can also use import path strings:
api = BoltAPI(
middleware=[
DjangoMiddleware("django.contrib.sessions.middleware.SessionMiddleware"),
DjangoMiddleware("myapp.middleware.CustomMiddleware"),
]
)
Using DjangoMiddlewareStack¶
When using multiple Django middleware, DjangoMiddlewareStack is more efficient as it performs a single request conversion instead of one per middleware:
from django_bolt import BoltAPI
from django_bolt.middleware import DjangoMiddlewareStack
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth.middleware import AuthenticationMiddleware
api = BoltAPI(
middleware=[
DjangoMiddlewareStack([
SessionMiddleware,
AuthenticationMiddleware,
])
]
)
Accessing Django request attributes¶
Django middleware sets attributes on the request that are automatically synced to the Bolt request:
@api.get("/profile")
async def profile(request):
# User from AuthenticationMiddleware (async)
user = await request.auser()
# Session from SessionMiddleware
session = request.session
theme = await session.aget("theme", "light")
# Messages from MessageMiddleware
messages = request.state.get("_messages")
return {
"username": user.username if user.is_authenticated else "anonymous",
"authenticated": user.is_authenticated,
"session_key": session.session_key,
"theme": theme,
}
Available synced attributes:
| Attribute | Source | Access |
|---|---|---|
| User (async) | AuthenticationMiddleware | await request.auser() |
| User (sync) | AuthenticationMiddleware | request.user |
| Session | SessionMiddleware | request.session |
| Messages | MessageMiddleware | request.state["_messages"] |
| META | All middleware | request.state["META"] |
| CSRF token | CsrfViewMiddleware | request.state["_csrf_token"] |
Performance notes¶
Django-Bolt optimizes middleware execution with a three-tier system:
- Django built-in middleware - Executed directly without thread pool overhead (fastest)
- Third-party middleware with hooks - Wrapped in
sync_to_asyncfor safety - Custom
__call__middleware - Executed as a chain via singlesync_to_asynccall
The DjangoMiddlewareStack automatically categorizes your middleware for optimal performance.
If a DjangoMiddlewareStack mixes hook middleware (process_request / process_view / process_response) with __call__-only middleware, Django-Bolt uses a correctness-first compatibility path to preserve strict declared order and hook semantics. This path is slower than the pure hook fast path.
Middleware order¶
Python middleware execution order is explicit and strict:
- Global middleware (
BoltAPI(middleware=[...])) - Router middleware (parent router to child router)
- Route middleware (
@middleware(...)/ function-style@middleware) - Handler
For responses, the order is reversed.
Rust-handled middleware configs (for example @cors and @rate_limit) are still compiled from metadata and executed in Rust.
Performance¶
Django-Bolt's middleware runs in Rust where possible:
- CORS preflight handling
- Rate limiting with token bucket
- Response compression
This means these operations don't acquire the Python GIL, enabling higher throughput.