Demystifying middleware in Django’s request-response cycle

I dived into Django’s source code to create a comprehensive diagram describing every step of Django’s request-response cycle through all layers of the middleware onion.

I’ve been building my first web application in Django, involving some customised exception handling, logging and auditing. I figured middleware would be the most appropriate mechanism to implement these features, since I want them to apply to all views.

However there are actually multiple middleware components and this started to raise confusion about where or how to implement each piece of functionality. I first needed to get my head around these different components and how they interacted with the request-response flow.

The official documentation describes the different middleware types, which can all be implemented by a single middleware class:

Request middleware — process the request before URL resolution & view routing

— process the request before URL resolution & view routing View middleware — process_view() — after URL resolution, the view function/class is known and URL parameters are provided

— — after URL resolution, the view function/class is known and URL parameters are provided Exception middleware — process__exception() — executed when view raises exception

— — executed when view raises exception Template response middleware — process_template_response() —executed when view returns TemplateResponse or equivalent

— —executed when view returns TemplateResponse or equivalent Response middleware — Final processing of HTTPResponse before being sent to client

There’s also plenty of flow diagrams online, but most present a simplified high-level view. I was starting to get an idea of how everything works, and this level of understanding is probably sufficient for the majority of use cases, but I wanted to go deeper. How exactly did view middleware differ to request middleware, when and how was it called? What were the exact conditions for template response to be executed, did the response have to subclass TemplateResponse or just have a render() method? How were exceptions handled for all the different middleware types?

To answer these questions I consulted the ultimate source of truth, the code for Django’s base handler class. I used this to construct a comprehensive flow diagram which aims to capture all possible outcomes at every step of the journey.

Detailed Django request-response diagram (red lines indicate exception handling)

Some interesting things to note:

ALL Request middleware is executed before any View middleware

View middleware can return a TemplateResponse equivalent or a standard HTTPResponse

TemplateResponse equivalent only needs to have a callable render() method for Template Middleware to be executed, so you can create your own custom intermediate response classes

method for Template Middleware to be executed, so you can create your own custom intermediate response classes Exceptions raised by this render() method cause exception middleware to be executed, not Django standard error handling

method cause exception middleware to be executed, not Django standard error handling Django exception handling is responsible for generating standard error views (404 — page not found, 500 — server error, 403 — forbidden, etc)

Hopefully this diagram can help you clear up any confusion like I had. Let me know if you think I’ve interpreted something wrong or left anything out!