DOCUMENTATION / CORE FRAMEWORK

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.