Introduction to Django’s Request Pipeline
This article introduces Django’s request pipeline and explains how an HTTP request moves through the web server, WSGI or ASGI, middleware, URL resolution, views, forms, models, templates, and back as an HTTP response. It also covers authentication, sessions, CSRF, redirects, errors, static and media requests, debugging, and common request-handling mistakes.
Introduction to Django’s Request Pipeline
When a browser opens a Django page, a surprising amount of work happens before HTML appears on the screen.
A request such as:
⧉
1 | |
does not go directly to a template.
Instead, it moves through several layers of Django.
A simplified request pipeline looks like this:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Understanding this pipeline makes many Django concepts easier to connect.
It helps explain:
- where
requestcomes from - how URLs choose views
- when middleware runs
- how authentication reaches a view
- where database queries happen
- how templates become HTML
- how redirects and errors are returned
- why some behavior affects every request
What Is an HTTP Request?
A browser communicates with a web application using HTTP.
A request contains information such as:
- HTTP method
- URL
- headers
- cookies
- query parameters
- submitted form data
- uploaded files
- request body
A simple request may look conceptually like:
⧉
1 2 3 4 | |
Django converts the incoming request into an HttpRequest object.
The view receives that object as its first argument:
⧉
1 2 | |
The Request and Response Cycle
At a high level, Django receives a request and must return a response.
⧉
1 2 3 4 5 | |
A function-based view demonstrates this directly:
⧉
1 2 3 4 5 | |
The input is:
⧉
1 | |
The output is:
⧉
1 | |
Almost everything in Django’s request pipeline exists to help determine what should happen between those two points.
The Web Server Receives the Request
In production, Django is normally not the first process that receives an HTTP request.
A request may first reach:
- Nginx
- Apache
- a cloud load balancer
- a reverse proxy
- a platform router
- another web server
The web server may handle things such as:
- HTTPS
- static files
- request buffering
- compression
- proxy headers
- connection management
Dynamic application requests are then passed to Django through an application server.
A production architecture might look like:
⧉
1 2 3 4 5 6 7 | |
or:
⧉
1 2 3 4 5 6 7 | |
During local development:
⧉
1 | |
provides a development server so these external pieces are not usually needed.
WSGI and ASGI
Django applications can run through two main interfaces:
⧉
1 2 | |
WSGI is the traditional synchronous Python web-server interface.
ASGI supports asynchronous application behavior as well as traditional synchronous requests.
A Django project normally contains:
⧉
1 2 3 | |
These files expose the Django application to the application server.
Conceptually:
⧉
1 2 3 4 5 | |
Most beginner Django code does not need to interact directly with these files.
Django Creates an HttpRequest
Once Django receives the request, it creates an HttpRequest object.
A view might inspect:
⧉
1 2 3 4 | |
Common request attributes include:
⧉
1 2 3 4 5 6 7 8 9 | |
For example:
⧉
1 | |
may contain:
⧉
1 | |
or:
⧉
1 | |
Query Parameters
Consider:
⧉
1 | |
Django places the query-string parameters in:
⧉
1 | |
Example:
⧉
1 2 | |
The name request.GET does not mean it contains all data from every GET request.
It specifically contains query-string parameters.
Submitted Form Data
For a normal POST form:
⧉
1 | |
submitted values are commonly available through:
⧉
1 | |
Example:
⧉
1 | |
In a normal Django application, forms should usually validate this input instead of reading raw values directly:
⧉
1 2 3 4 | |
Uploaded Files
Files submitted through:
⧉
1 2 3 4 | |
appear in:
⧉
1 | |
Example:
⧉
1 | |
Django keeps ordinary form data and uploaded files separate.
Middleware Enters the Pipeline
Before the request reaches the view, it passes through Django middleware.
Middleware is code that can inspect or modify requests and responses globally.
The middleware configuration is stored in settings.py:
⧉
1 2 3 4 5 6 7 8 9 | |
Middleware can affect many or all requests.
Examples include:
- sessions
- authentication
- CSRF protection
- security headers
- messages
- redirects
- logging
Middleware Order Matters
Middleware is ordered.
Conceptually, incoming requests move down the list:
⧉
1 2 3 4 5 6 7 | |
Responses move back through the middleware in reverse:
⧉
1 2 3 4 5 6 7 | |
This creates an onion-like structure:
⧉
1 2 3 4 5 6 7 | |
Because of this, middleware order can affect application behavior.
A Simple Middleware Example
A basic custom middleware might look like:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
The flow is:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Middleware Can Return a Response Early
Middleware does not always need to allow the request to reach the view.
It can return a response immediately.
Conceptually:
⧉
1 2 3 4 5 6 7 8 | |
In that case:
⧉
1 2 3 4 5 | |
The URL resolver and view may never run.
This is useful for behavior such as:
- access restrictions
- redirects
- rate limiting
- maintenance mode
Session Middleware
Django’s session middleware gives the request access to:
⧉
1 | |
Example:
⧉
1 | |
Later:
⧉
1 | |
Without the session middleware, normal session functionality would not be attached to the request.
Authentication Middleware
Authentication middleware connects the current user to:
⧉
1 | |
Example:
⧉
1 2 3 | |
The authentication system uses session information to determine which user is associated with the request.
Conceptually:
⧉
1 2 3 4 5 6 7 | |
This is one reason middleware ordering matters.
CSRF Middleware
For state-changing requests such as many POST submissions, Django’s CSRF middleware checks for a valid CSRF token.
Template:
⧉
1 2 3 4 | |
The request passes through:
⧉
1 | |
before normal view processing succeeds.
If the token is missing or invalid, Django may return a 403 response before the intended view completes.
URL Resolution
After request middleware processing, Django determines which view should handle the requested path.
Suppose the request is:
⧉
1 | |
The project URL configuration may contain:
⧉
1 2 3 4 5 6 7 8 9 | |
Then articles/urls.py might contain:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
Django matches:
⧉
1 | |
and determines:
⧉
1 2 3 4 5 | |
URL Patterns Are Checked in Order
Django examines URL patterns from top to bottom.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 | |
The specific route is placed first.
If broad routes are placed before more specific routes, unexpected matches may occur.
URL Converters
Django URL patterns can convert path values.
Example:
⧉
1 2 3 4 | |
The converter:
⧉
1 | |
means the view receives an integer:
⧉
1 2 | |
Other common converters include:
⧉
1 2 3 4 5 | |
The View Is Called
Once Django resolves the URL, it calls the selected view.
Function-based view:
⧉
1 2 | |
The view receives:
⧉
1 2 | |
It is responsible for eventually producing an HTTP response.
For example:
⧉
1 2 3 4 5 6 7 | |
Class-Based Views
With a class-based view:
⧉
1 2 3 4 5 6 | |
the URL uses:
⧉
1 2 3 4 | |
as_view() creates a callable that Django can use like a normal view.
It then dispatches the request based on the HTTP method.
Conceptually:
⧉
1 2 3 4 5 6 7 | |
The View Coordinates Application Logic
A view usually coordinates other parts of the application rather than doing everything itself.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
This view:
- receives the request
- queries the model
- handles a missing object
- chooses a template
- supplies context data
- returns a response
Database Queries
A view may use Django’s ORM:
⧉
1 2 3 | |
Conceptually:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
The browser never communicates directly with the database.
The request passes through Django application logic first.
QuerySets Are Often Lazy
Consider:
⧉
1 2 3 | |
This creates a QuerySet.
The SQL query may not execute immediately.
It commonly executes when Django actually needs the results, such as during:
⧉
1 | |
iteration:
⧉
1 2 | |
or template rendering:
⧉
1 2 3 | |
This matters when reasoning about where database work occurs during the request pipeline.
Service Functions
Larger applications may move business operations out of views.
Instead of:
⧉
1 2 3 4 5 6 7 | |
a view might call:
⧉
1 2 3 4 | |
The view remains responsible for HTTP concerns:
⧉
1 2 3 4 | |
while service functions handle application workflows.
Forms in the Request Pipeline
Forms often sit between incoming POST data and business logic.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
The flow is:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Authentication and Permissions
A view may check whether the current user is allowed to continue.
Example:
⧉
1 2 3 4 5 6 7 8 | |
Conceptually:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Permissions may add another check:
⧉
1 2 3 4 | |
Shortcuts Can Return Responses
Django provides several shortcuts that simplify common pipeline operations.
render():
⧉
1 2 3 4 5 6 7 | |
creates an HTTP response from a template.
redirect():
⧉
1 2 3 | |
creates a redirect response.
get_object_or_404():
⧉
1 2 3 4 | |
raises a 404 condition when an object is missing.
Template Rendering
A view often passes data into a template.
⧉
1 2 3 4 5 6 7 | |
Template:
⧉
1 2 3 4 5 | |
The template engine combines:
⧉
1 2 3 4 5 | |
For example:
⧉
1 2 3 4 5 | |
The rendered HTML becomes the body of an HTTP response.
Context Processors
Some template variables are made available through context processors.
For example, depending on configuration, templates may access:
⧉
1 | |
or:
⧉
1 | |
without every view manually adding them.
Conceptually:
⧉
1 2 3 4 5 | |
Context processors are another example of framework-level behavior that participates indirectly in the request pipeline.
HttpResponse
Every successful Django view ultimately produces an HTTP response.
Basic response:
⧉
1 2 3 4 5 6 | |
A response contains information such as:
- status code
- headers
- cookies
- response body
Example:
⧉
1 2 3 4 | |
Common Response Types
Django provides several response classes and helpers.
Normal response:
⧉
1 | |
JSON:
⧉
1 2 3 4 5 6 7 8 | |
Redirect:
⧉
1 2 3 | |
File:
⧉
1 2 3 4 | |
Not found:
⧉
1 2 3 4 | |
HTTP Status Codes
Responses include a status code.
Common examples include:
| Status | Meaning |
|---|---|
200 |
Successful response |
201 |
Resource created |
302 |
Redirect |
400 |
Bad request |
403 |
Forbidden |
404 |
Not found |
500 |
Server error |
The status code tells the browser or API client how the request was handled.
Redirect Responses
A redirect does not directly display the final page.
Suppose a view returns:
⧉
1 2 3 4 | |
The pipeline becomes:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
The redirect causes a completely new HTTP request.
This is important when understanding the Post/Redirect/Get pattern.
Exceptions in Views
Suppose a view raises an exception:
⧉
1 2 3 4 | |
Django catches the exception higher in the request-handling system.
The result depends on:
- exception type
- middleware
DEBUG- configured error handlers
For example:
⧉
1 | |
normally becomes a 404 response.
An unhandled programming error normally becomes a 500 response.
404 Handling
A common pattern is:
⧉
1 2 3 4 | |
If the object exists:
⧉
1 | |
If it does not:
⧉
1 2 3 | |
With DEBUG=False, Django can render a custom 404.html template.
500 Handling
Unhandled server errors normally result in:
⧉
1 | |
During development with:
⧉
1 | |
Django displays a detailed technical error page.
In production, detailed debug pages should not be exposed to users.
Response Middleware
After the view returns a response, that response travels back outward through middleware.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Middleware may modify:
- headers
- cookies
- status codes
- response bodies
before the response leaves Django.
Security Headers
Security middleware may add or enforce HTTP security behavior.
For example, production responses may include headers related to:
- HTTPS
- content-type protection
- referrer behavior
- other browser security mechanisms
This happens after or around normal view processing rather than being repeated inside every individual view.
Cookies
A response can set cookies:
⧉
1 2 3 4 5 6 7 8 9 10 | |
The browser receives the cookie in the response.
On later requests, the browser may send it back:
⧉
1 2 3 | |
The flow is:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Sessions Across Requests
Sessions build on the request-response cycle.
A view may write:
⧉
1 | |
Django associates that session data with the browser.
On a future request:
⧉
1 2 3 4 | |
The important point is that HTTP requests themselves are independent.
Sessions provide application-level continuity between those separate requests.
Messages Across Redirects
Django’s messages framework can preserve short messages across a redirect.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
On the following request, a template can display:
⧉
1 2 3 | |
The flow is:
⧉
1 2 3 4 5 6 7 8 9 | |
Static Files Follow a Different Path
A template may contain:
⧉
1 2 3 4 | |
The browser first receives the HTML.
It then makes another request:
⧉
1 | |
In production, that request is often handled by:
- Nginx
- a CDN
- object storage
- another static-file service
rather than by the normal Django view pipeline.
A page load may therefore involve many separate HTTP requests.
Media Files May Also Follow a Different Path
An uploaded image might appear as:
⧉
1 2 3 4 | |
The browser then sends another request for the media URL.
In production, media may be served by:
- a web server
- object storage
- a CDN
- a protected Django download view
Whether media passes through Django depends on the deployment and security requirements.
API Requests Use the Same General Pipeline
A JSON API request still enters through the same broad Django request system.
For example:
⧉
1 | |
may flow through:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
The difference is mainly in how the view interprets the input and what response it returns.
Instead of HTML:
⧉
1 | |
an API may return JSON:
⧉
1 | |
Django REST Framework adds additional layers such as:
- parsers
- authentication
- permissions
- serializers
- content negotiation
but it still operates within Django’s broader request-response system.
Synchronous Requests
A traditional synchronous Django view looks like:
⧉
1 2 | |
During its execution, work occurs in sequence.
Conceptually:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
The request finishes before that execution context handles another piece of work.
Asynchronous Views
Django can also use asynchronous views:
⧉
1 2 | |
Async views are useful when a request spends time waiting on compatible asynchronous I/O operations.
The overall conceptual pipeline remains similar:
⧉
1 2 3 4 5 6 7 8 9 | |
However, synchronous and asynchronous components can interact differently depending on how the application is deployed.
Beginners should first understand the ordinary request-response model before optimizing around asynchronous execution.
Request Pipeline Example
Consider:
⧉
1 | |
The project contains:
⧉
1 2 3 4 5 6 | |
The application URLs contain:
⧉
1 2 3 4 5 6 7 | |
The view contains:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The request pipeline is approximately:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
POST Request Example
Now consider creating an article.
Browser submits:
⧉
1 | |
The request pipeline may be:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
The redirect creates a second complete request pipeline.
Login Example
Suppose an anonymous user requests:
⧉
1 | |
The request may flow like:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
After login:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
This demonstrates how sessions, middleware, authentication, and redirects all connect through the request pipeline.
Where Business Logic Should Live
The pipeline does not mean every operation belongs in the view.
A healthy separation might be:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
The view controls the HTTP flow.
The service handles the main business operation.
Request Pipeline and Database Transactions
Django does not automatically wrap every possible request in a database transaction unless the project is configured to do so.
Application code may use:
⧉
1 2 3 4 5 | |
A transaction controls database operations, not the entire HTTP request lifecycle.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
External side effects such as:
- sending email
- deleting files
- calling external APIs
do not automatically roll back when a database transaction fails.
Signals During a Request
Signals can also execute while the request is being processed.
For example:
⧉
1 | |
may trigger:
⧉
1 2 | |
The view does not necessarily call the signal receiver explicitly.
Conceptually:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
This is one reason excessive signal usage can make request behavior difficult to trace.
Logging the Pipeline
Logging is useful for understanding request flow.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Middleware can also log requests globally.
Useful information may include:
- method
- path
- status code
- duration
- user
- request ID
Avoid logging:
- passwords
- session cookies
- authentication tokens
- sensitive personal data
Request IDs
Larger systems often assign each request a unique identifier.
Example:
⧉
1 | |
The same ID can be included in logs from:
⧉
1 2 3 4 5 | |
This makes it easier to reconstruct what happened during one request.
Performance Through the Pipeline
A slow request may spend time in different places.
For example:
⧉
1 2 3 4 5 | |
Total:
⧉
1 | |
Understanding the pipeline helps locate where the delay actually occurs.
Common causes include:
- too many database queries
- slow external services
- expensive middleware
- large template workloads
- file operations
- repeated authentication or permission queries
Middleware Should Usually Stay Focused
Because middleware can affect every request, expensive middleware can become expensive for the entire application.
Good middleware candidates include:
- request logging
- global security behavior
- authentication-related processing
- request IDs
- broad redirects
Poor middleware candidates often include:
- feature-specific database workflows
- unrelated business logic
- large numbers of database queries
- behavior needed by only one view
Use middleware for cross-cutting request concerns.
Common Beginner Mistakes
Thinking URLs Execute Business Logic
A URL pattern should normally map a path to a view.
It should not contain the application workflow itself.
⧉
1 2 3 | |
not:
⧉
1 2 3 | |
Putting Everything in the View
Views can quickly become difficult to maintain when they contain:
- validation
- database operations
- emails
- payment logic
- complex permissions
- file processing
- external APIs
Use forms, models, services, and other appropriate layers.
Reading request.POST Without Validation
Avoid:
⧉
1 | |
for significant user input.
Prefer:
⧉
1 2 3 4 5 6 | |
Confusing Query Parameters and POST Data
Query string:
⧉
1 | |
uses:
⧉
1 | |
POST form:
⧉
1 | |
uses:
⧉
1 | |
Assuming Every Request Reaches a View
Middleware may return a response first.
CSRF validation may fail.
URL resolution may produce a 404.
An exception may occur before normal view completion.
Assuming a Redirect Continues the Same Request
A redirect ends the current request.
The browser then sends another request.
⧉
1 2 3 4 5 | |
Confusing Static Asset Requests With the Main Page Request
Loading one page may generate many HTTP requests:
⧉
1 2 3 4 | |
These are separate requests.
Assuming request.user Appears Automatically
Authentication middleware is part of the configuration that provides normal request.user behavior.
Ignoring Middleware Order
Some middleware depends on earlier middleware.
For example, authentication normally relies on session functionality.
Running Expensive Work in Middleware
Middleware can execute for a very large portion of application traffic.
Keep it focused.
Returning Something Other Than a Response
A view must ultimately return a valid HTTP response.
Incorrect:
⧉
1 2 3 4 | |
Correct:
⧉
1 2 3 4 5 6 7 8 | |
Debugging the Request Pipeline
When a request behaves unexpectedly, trace it layer by layer.
Start with:
⧉
1 2 3 4 5 6 7 8 9 10 | |
The browser’s network tools can reveal:
- request URL
- method
- status code
- request headers
- response headers
- redirects
- response body
Django logging can reveal the server-side path through the application.
A Minimal Request Pipeline
For a basic view:
⧉
1 2 3 4 5 | |
the simplified pipeline is:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
A Database-Backed Request Pipeline
For:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
the flow expands:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
A Form Submission Pipeline
For a creation form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
This is one of the most common request patterns in a Django application.
Recommended Mental Model
When working on a Django feature, think about the request in this order:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
This mental model is often more useful than thinking about views, forms, models, and middleware as isolated Django topics.
Mini Reference
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | |
Basic flow:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Database flow:
⧉
1 2 3 4 5 | |
Template flow:
⧉
1 2 3 4 5 6 7 8 9 | |
Django’s request pipeline describes the path an HTTP request takes through the application before a response is returned.
The main ideas are:
- a browser sends an HTTP request
- a web or application server passes the request to Django
- Django creates an
HttpRequest - middleware can inspect or modify the request
- the URL resolver selects a view
- the view coordinates application behavior
- forms can validate incoming data
- models and the ORM communicate with the database
- templates can render HTML
- the view returns an
HttpResponse - middleware can modify the outgoing response
- the server sends the response back to the browser
- redirects start a new request
- static and media files may follow separate request paths
- authentication, sessions, CSRF, and messages all participate in the broader request-response cycle
Once this pipeline is clear, many Django features stop looking like separate pieces.
URLs decide where the request goes. Middleware controls what happens around the request. Views decide what the application should do. Models handle persistent data. Forms handle input validation. Templates determine how HTML is rendered. Responses determine what is sent back to the client.
Together, these pieces form the request-response cycle at the center of every Django application.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.