Writing Custom Middleware

Hook into every request and response for cross-cutting concerns like timing, request IDs and tenant resolution.

Middleware is a layer that wraps every request on the way in and every response on the way out. It is the right home for concerns that apply to the whole site: request logging, timing headers, correlation IDs, tenant resolution, forcing a locale. If you find yourself copying the same two lines into every view, it probably belongs in middleware.

The modern middleware shape

Since Django 1.10 middleware is a callable factory. You get the request before the view runs and the response after — an onion, each layer wrapping the next.

class TimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response   # called ONCE at startup

    def __call__(self, request):
        import time
        start = time.monotonic()
        response = self.get_response(request)   # run the view (and inner middleware)
        response['X-Response-Time-ms'] = int((time.monotonic() - start) * 1000)
        return response

Order matters: middleware listed first in MIDDLEWARE is outermost. Security middleware goes near the top, your app-specific ones lower. You can also implement process_exception() and process_view() hooks for finer control.

Example

Example · python
import time
import uuid
import logging
from django.utils.deprecation import MiddlewareMixin

logger = logging.getLogger('request')


class RequestContextMiddleware:
    """Attach a correlation ID, resolve the tenant, and log timing — once per request."""

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # --- inbound: enrich the request ---
        request.id = request.headers.get('X-Request-ID', uuid.uuid4().hex)
        host = request.get_host().split(':')[0]
        request.tenant = Tenant.objects.filter(domain=host).first()
        start = time.monotonic()

        response = self.get_response(request)

        # --- outbound: annotate and log ---
        elapsed_ms = int((time.monotonic() - start) * 1000)
        response['X-Request-ID'] = request.id
        response['X-Response-Time-ms'] = str(elapsed_ms)
        logger.info(
            'request',
            extra={
                'request_id': request.id,
                'path': request.path,
                'method': request.method,
                'status': response.status_code,
                'ms': elapsed_ms,
                'tenant': getattr(request.tenant, 'slug', None),
            },
        )
        return response

    def process_exception(self, request, exception):
        # Correlate the crash with the same request id and re-raise.
        logger.exception('unhandled', extra={'request_id': getattr(request, 'id', '-')})
        return None   # let Django's normal 500 handling proceed


# settings.py — order is outermost-first:
# MIDDLEWARE = [
#     'django.middleware.security.SecurityMiddleware',
#     'core.middleware.RequestContextMiddleware',
#     ...
# ]

When to use it

  • A developer writes middleware to attach the current user's active tenant to every request for a multi-tenant SaaS app.
  • A team adds request-timing middleware that logs response time and request path to a monitoring service.
  • A developer uses middleware to enforce a maintenance mode by returning a 503 page without modifying any view.

More examples

Simple middleware class

Measures the time between receiving the request and returning the response and adds it as a header.

Example · python
class RequestTimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        import time
        start = time.monotonic()
        response = self.get_response(request)
        elapsed = (time.monotonic() - start) * 1000
        response["X-Response-Time"] = f"{elapsed:.1f}ms"
        return response

Register middleware in settings

Inserts the custom middleware class into the MIDDLEWARE list; order matters for request/response flow.

Example · python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "myapp.middleware.RequestTimingMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    # ...
]

Maintenance mode middleware

Returns a 503 response for every request when MAINTENANCE_MODE is True in settings.

Example · python
from django.http import HttpResponse

class MaintenanceModeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        from django.conf import settings
        if getattr(settings, "MAINTENANCE_MODE", False):
            return HttpResponse("Down for maintenance", status=503)
        return self.get_response(request)

Discussion

  • Be the first to comment on this lesson.
Writing Custom Middleware — Django | SoundsCode