Middleware lets you run code before and after every request. JustAPI has two middleware layers:
- Native Rust middleware (CORS, JWT, security headers) — configured with dedicated methods, runs on every request including the native fast path.
- Python middleware — your own
async def mw(request, call_next)callables, registered with@app.middleware("http")or per-routemiddlewares=.
Basic HTTP Middleware
Use the @app.middleware("http") decorator. Middleware is async and must
await call_next(request):
import time
from justapi import JustAPIApp
app = JustAPIApp()
@app.middleware("http")
async def timing_middleware(request, call_next):
start = time.time()
response = await call_next(request)
elapsed = time.time() - start
response.headers.append(
(b"X-Process-Time", str(round(elapsed, 4)).encode())
)
return response
call_next dispatches the request down the chain; the return value is the
handler's response object.
Performance: Python middleware runs on the Python side of the boundary and is bypassed (must be) by the native fast path. Prefer native middleware (
add_cors,enable_secure_headers,set_jwt_auth) when possible. Keep Python middleware to logic that must run in Python.
CORS Middleware
JustAPI has a built-in, Rust-native CORS configuration method:
app.add_cors(
allow_origins=["https://example.com", "https://api.example.com"],
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
allow_credentials=True,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
allow_origins |
list[str] | ["*"] |
Allowed origins |
allow_methods |
list[str] | All methods | Allowed HTTP methods |
allow_headers |
list[str] | ["*"] |
Allowed headers |
allow_credentials |
bool | False |
Allow cookies/auth headers |
Secure Headers Middleware
Add safe HTTP security headers (Rust-native):
app.enable_secure_headers(with_hsts=True)
This adds:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Strict-Transport-Security (when with_hsts=True)
- Content-Security-Policy
- X-XSS-Protection: 0
Multiple Middleware
Middlewares are executed in registration order (first added is outermost):
@app.middleware("http")
async def middleware_one(request, call_next):
print("Before 1")
response = await call_next(request)
print("After 1")
return response
@app.middleware("http")
async def middleware_two(request, call_next):
print("Before 2")
response = await call_next(request)
print("After 2")
return response
Output for a single request:
Before 1
Before 2
[handler executes]
After 2
After 1
Route-Level Middleware
Attach middleware to individual routes:
async def auth_middleware(request, call_next):
if not (request.get("headers") or {}).get("x-token"):
return {"status": 401, "body": {"detail": "unauthorized"}}
return await call_next(request)
@app.get("/protected", middlewares=[auth_middleware])
async def protected(request):
return {"message": "top secret"}
Real-World Patterns
Request Logging Middleware
import logging
logger = logging.getLogger("justapi")
@app.middleware("http")
async def log_requests(request, call_next):
method = request.get("method")
path = request.get("path")
logger.info("→ %s %s", method, path)
response = await call_next(request)
status = getattr(response, "status_code", None) or 200
logger.info("← %s %s → %s", method, path, status)
return response
Authorization via Dependency
from justapi import Depends, HTTPException, Header
def require_admin(authorization: str = Header(...)):
if authorization != "Bearer admin-token":
raise HTTPException(403, "Admin access required")
@app.get("/admin/users", dependencies=[Depends(require_admin)])
def list_all_users(request):
return {"users": []}
Native Fast-Path Note
The Rust native fast path (native=True) is a validate-and-echo shortcut that
does not execute Python middleware or dependencies. Routes
using native=True must not declare Python middleware/dependencies — the
framework rejects that combination at registration time. Native middleware
(add_cors, enable_secure_headers, set_jwt_auth) runs for all routes.
How It Works Internally
- Native middleware (CORS, security headers, JWT, rate limit) runs in the Rust middleware chain with zero GIL overhead.
- Python middleware runs as an async wrapper around your handler on the Python side.
call_nextdispatches down the middleware chain to the handler.- The handler's result flows back through each middleware's post-processing.
Next Steps
- Error Handling — Custom error responses
- Dependency Injection — Reusable components
- Resilience Patterns — Circuit breakers, rate limiting