Middleware and request lifecycle
Middleware performs work around a handler: logging, authentication, headers, or request limits.
1. Create a reusable check
Rather than repeating authorization logic in every handler, separate it into a helper:
def require_api_key(req: Request) -> Bool:
return req.headers.get("X-API-Key", "") == "example-dev-key"
def protected(req: Request) -> Response:
if not require_api_key(req):
return Response.json('{"error":"API key required"}', status=401)
return Response.json('{"message":"Welcome"}')
Register app.get("/api/private", protected).
Explanation: require_api_key is a reusable function called before application logic. In a real application, read secrets from configuration rather than hard-coding them.
2. Send two requests
Without the header:
curl -i http://127.0.0.1:8000/api/private
Expected status: 401.
With a header:
curl -i -H "X-API-Key: example-dev-key" http://127.0.0.1:8000/api/private
Expected status: 200, with a welcome message.
3. Apply the pattern to multiple routes
def reports(req: Request) -> Response:
if not require_api_key(req):
return Response.json('{"error":"Unauthorized"}', status=401)
return Response.json('{"reports":[]}')
Register app.get("/api/reports", reports). The same check now protects both handlers.
4. Middleware ordering
When using application-wide middleware, perform request-size checks and authentication before processing sensitive data. Add security and CORS headers to the outgoing response. Handle exceptions consistently so clients receive JSON errors instead of an internal traceback.
Next: Authentication explains login/session behavior; Security covers handling secrets and user input.