Writing Middleware¶
Middleware sits between the server and your route handlers, processing every request and response that flows through your application. It’s the right tool for cross-cutting concerns — things that apply to all requests, not just specific routes.
Common middleware use cases:
Request logging and timing
Authentication and authorization
Adding security headers
Request ID generation
Rate limiting
Response compression (built-in)
Hooks vs. Middleware¶
Responder gives you two levels of request processing:
Hooks (before_request / after_request) run inside Responder’s
routing layer. They receive Responder’s req and resp objects and
are the simplest way to add behavior:
@api.route(before_request=True)
def add_header(req, resp):
resp.headers["X-Powered-By"] = "Responder"
@api.after_request()
def log_request(req, resp):
print(f"{req.method} {req.url.path} -> {resp.status_code}")
Middleware runs at the ASGI level, wrapping the entire application. It’s more powerful but more complex — you work with raw ASGI scopes instead of Responder objects. Use middleware when you need to process requests before they reach Responder’s routing, or when you need to integrate with Starlette middleware.
Using Starlette Middleware¶
Responder is built on Starlette, so any Starlette middleware works out of the box:
from starlette.middleware.base import BaseHTTPMiddleware
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
import time
start = time.time()
response = await call_next(request)
duration = time.time() - start
response.headers["X-Response-Time"] = f"{duration:.3f}s"
return response
api.add_middleware(TimingMiddleware)
The dispatch method receives a Starlette Request and a
call_next function. Call call_next(request) to pass the request
to the next middleware (or to your route handler). The return value is
a Starlette Response that you can modify before it’s sent.
Built-in Middleware¶
Responder configures several middleware components automatically:
GZipMiddleware — compresses responses larger than 500 bytes
TrustedHostMiddleware — validates the
HostheaderServerErrorMiddleware — catches unhandled exceptions
ExceptionMiddleware — routes exceptions to your handlers
SessionMiddleware — manages signed cookie sessions
Optional middleware you can enable:
CORSMiddleware —
api = responder.API(cors=True)HTTPSRedirectMiddleware —
api = responder.API(enable_hsts=True)
Adding Third-Party Middleware¶
Any ASGI middleware can be added with api.add_middleware():
from some_package import SomeMiddleware
api.add_middleware(SomeMiddleware, option1="value", option2=True)
Keyword arguments are passed to the middleware’s constructor.
Middleware Order¶
Middleware wraps your application like layers of an onion. The last middleware added is the outermost layer — it sees the request first and the response last.
Responder’s built-in middleware stack (from outermost to innermost):
SessionMiddleware
ServerErrorMiddleware
CORSMiddleware (if enabled)
TrustedHostMiddleware
HTTPSRedirectMiddleware (if enabled)
GZipMiddleware
ExceptionMiddleware
Your routes
When you call api.add_middleware(), your middleware is added outside
the existing stack. Keep this in mind for ordering dependencies — if
middleware A depends on middleware B having run first, add B before A.
When to Use What¶
Simple header additions, logging, auth checks → use hooks
Response transformation, timing, third-party integrations → use middleware
Rate limiting → use the built-in
RateLimiter(it uses hooks internally)Request ID → use
api = responder.API(request_id=True)
Start with hooks. They’re simpler and cover most cases. Graduate to middleware when hooks aren’t enough.