Django

Django Middleware👨‍💻

Middleware is Django's hook into request/response processing. Every HTTP request passes through a chain of middleware components before reaching your view, and every response passes back through the same chain in reverse. Middleware can inspect, modify, or short-circuit requests and responses globally, without touching individual views.

Django ships with essential middleware for security, sessions, authentication, CSRF protection, and more. Django 6 introduced ContentSecurityPolicyMiddleware for native CSP header management. You can also write custom middleware for cross-cutting concerns like logging, performance monitoring, rate limiting, and request enrichment. Understanding middleware ordering and the request/response lifecycle is critical to building secure and performant Django applications.

Key Takeaways

  • 1Middleware processes requests top-down through the MIDDLEWARE list and responses bottom-up — ordering determines which middleware runs first
  • 2Each middleware is a callable that receives `get_response` (the next middleware or the view) and returns a callable that takes a `request` and returns a `response`
  • 3SecurityMiddleware should always be first in the list, and SessionMiddleware must come before AuthenticationMiddleware
  • 4Django 6 introduced `django.middleware.csp.ContentSecurityPolicyMiddleware` for built-in Content Security Policy support, replacing the need for the third-party django-csp package
  • 5Middleware can run code before the view (on the request path), after the view (on the response path), or both — this is the onion model of middleware
  • 6Async middleware is supported since Django 4.1 — define an `async` callable to avoid blocking the event loop in ASGI deployments

Master django middleware

Take the Django Security Best Practices course with hands-on lessons and challenges.

Examples

Middleware structure — the function-based pattern

python

Django middleware uses a closure pattern. The outer function receives get_response (the next layer) and is called once at startup. The inner function runs on every request. Code before get_response() executes on the request path; code after executes on the response path.

Custom logging middleware — track every request

python

A practical middleware that logs every request with its HTTP method, path, status code, and response time. Use time.monotonic() instead of time.time() for accurate duration measurement since it is not affected by system clock adjustments.

Class-based middleware with per-view hooks

python

Class-based middleware supports optional hooks: process_view runs after URL resolution but before the view, and process_exception runs when a view raises an unhandled exception. This maintenance mode example short-circuits all requests and returns a 503 JSON response without hitting any view.

Async middleware — non-blocking in ASGI deployments

python

Django auto-detects whether middleware is sync or async based on whether the inner callable is a coroutine function. The dual-mode pattern works under both WSGI and ASGI. For ASGI-only deployments, you can define a simpler async-only middleware. Avoid blocking I/O (database queries, file reads) in async middleware without using sync_to_async.

Django 6 ContentSecurityPolicyMiddleware — native CSP support

python

Django 6 introduced built-in CSP support via ContentSecurityPolicyMiddleware, replacing the third-party django-csp package. Configure policies in SECURE_CSP using the CSP helper class. Use SECURE_CSP_REPORT_ONLY to test policies without enforcement first. The csp context processor provides {{ csp_nonce }} in templates for nonce-based inline script authorization.

Common Mistakes

Mistake:

Placing middleware in the wrong order — for example, putting AuthenticationMiddleware before SessionMiddleware

Fix:

Django middleware has strict ordering requirements. SessionMiddleware must precede AuthenticationMiddleware (since auth reads from the session), and SecurityMiddleware should be first. Follow Django's default ordering and only insert custom middleware where it logically belongs (typically near the end).

Mistake:

Performing blocking I/O (database queries, HTTP calls, file reads) inside async middleware without wrapping it in sync_to_async

Fix:

In ASGI deployments, synchronous I/O blocks the event loop and kills concurrency. Wrap any blocking operation with `from asgiref.sync import sync_to_async` or use an async-native library (e.g., httpx instead of requests).

Mistake:

Catching all exceptions in middleware and silently returning a 500 — this swallows errors and hides bugs

Fix:

Let exceptions propagate to Django's error handling unless you have a specific reason to catch them. If you do catch exceptions in process_exception, always log the full traceback and re-raise or return an appropriate error response.

Mistake:

Adding expensive per-request logic (e.g., database queries, external API calls) in middleware that runs on every single request, including static files

Fix:

Guard expensive middleware logic with path checks (e.g., skip for /static/ or /media/ paths) or move the logic to a decorator or mixin applied only to the views that need it. Middleware runs on every request — keep it lightweight.

Best Practices

  • Keep middleware focused on a single cross-cutting concern — logging, security headers, timing, etc. If middleware does multiple unrelated things, split it into separate components
  • Use function-based middleware for simple cases and class-based middleware only when you need process_view or process_exception hooks
  • Always test middleware ordering by checking that request.user is available (AuthenticationMiddleware ran) and request.session exists (SessionMiddleware ran) in the views that depend on them
  • Use report-only mode when deploying Django 6's ContentSecurityPolicyMiddleware — enforce only after monitoring reports to avoid breaking legitimate scripts
  • Write middleware tests that verify both the request path (before the view) and response path (after the view) — test short-circuiting behavior separately
  • Prefer the dual sync/async pattern for middleware that needs to work under both WSGI and ASGI — check asyncio.iscoroutinefunction(get_response) and define both paths

Summary

Django middleware provides hooks into the request/response lifecycle for cross-cutting concerns like security, logging, and performance monitoring. Requests flow top-down through the MIDDLEWARE list and responses flow bottom-up. Each middleware is a callable wrapping get_response. Ordering matters — SecurityMiddleware first, SessionMiddleware before AuthenticationMiddleware. Django 6 added ContentSecurityPolicyMiddleware for native CSP support with nonce-based inline script authorization. For ASGI deployments, use async middleware to avoid blocking the event loop. Keep middleware lightweight since it runs on every request.

Practice Django with hands-on challenges

Learn django middleware hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Django with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.