How a request flows through Django
When someone opens a page in your Django application, a precise sequence of steps runs between their click and the HTML that comes back. Most bugs, and most confusion, come from not knowing where in that sequence your code sits. This lesson traces one request through Django end to end, so the framework stops being magic and becomes a pipeline you can reason about — and place your own code within confidently.
The one-sentence model
A Django application is, at heart, a function: an HTTP request comes in, and an HTTP response goes out. Everything Django does is machinery to route that request to your code — a view — and to help your view build the response. Hold that shape in your head: request in, response out, your view in the middle.
The journey of a request, step by step
Say a clinic receptionist visits http://nidaan.example.com/patients/. Here is what happens, in order:
- The web server receives the request and hands it to Django through WSGI or ASGI (the
wsgi.py/asgi.pyentry points from the setup lesson). In development,runserverplays the web server's role. - Middleware runs, on the way in. Django passes the request through a stack of middleware — small
layers that process every request. The built-in ones add security headers, attach the session, and
identify the logged-in user (
request.user). Middleware is whyrequest.useralready exists by the time your view runs. - URL resolution. Django takes the path
/patients/and matches it against your URL patterns (config/urls.pyand the appurls.pyfiles it includes), top to bottom, until one matches. The match determines which view function runs and captures any parameters from the URL. - Your view runs. This is your code. The view receives an
HttpRequestobject and does the actual work — usually query the database via the ORM, then render a template with the data. It must return anHttpResponse. - Middleware runs again, on the way out. The response travels back up through the middleware stack, which can modify it (adding headers, compressing, and so on).
- The response is sent back to the browser, which renders the HTML.
Request → middleware (in) → URL resolver → your view → middleware (out) → response. Your view is step 4, sandwiched in the middle, and almost everything you write lives there and in the template it renders.
Seeing it with the smallest possible view
Strip it to essentials. A URL pattern points a path at a view; the view returns a response. In the
patients app:
# patients/views.py
from django.http import HttpResponse
def home(request): # every view takes the request as its first argument
return HttpResponse("Nidaan is running.") # ...and returns an HttpResponse
# patients/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.home, name="home"), # the empty path "" is this app's root
]
# config/urls.py — the project's root URL map
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("patients.urls")), # hand "" and everything under it to the patients app
]
Visit the root URL and the browser shows "Nidaan is running." Trace it: the request arrived, middleware
ran, the resolver matched "" in config/urls.py, which included patients.urls, which matched ""
and called home, which returned an HttpResponse. That is the entire cycle, with your code as the single
custom step. Every richer page is this same path with more work inside the view.
include, and why URLs are split across files
Notice the root urls.py does not list every URL — it includes the patients app's urls.py. This is
how Django keeps URL configuration modular: each app owns its own URLs, and the project's root file just
delegates to each app. As Nidaan grows to have appointments, reports and billing apps, each brings
its own urls.py, and the root file stays a short table of contents. This mirrors Django's whole
philosophy — an application is a collection of self-contained apps, wired together at the top.
The two objects you will use constantly: request and response
Your view lives between two objects, and knowing them well is most of the job:
HttpRequest(therequestparameter) carries everything about the incoming request:request.method("GET","POST", …),request.GETandrequest.POST(query and form data),request.user(who is logged in, courtesy of middleware),request.path, headers, and more. You read from it to decide what to do.HttpResponseis what you must return. Usually you will not construct it by hand — you will use helpers likerender(request, "template.html", context)(which builds anHttpResponsefrom a template) orredirect("home"). But under every one of those is anHttpResponse, because that is the one thing a view is contractually required to return.
A view that does not return an HttpResponse is the most common early error — "The view didn't return an
HttpResponse object. It returned None instead." — and now you know exactly why Django insists: the response
is the output half of the request-in-response-out contract.
Where this leaves you
You now have the map. When a page 404s, you look at URL resolution (step 3) — no pattern matched. When
a page shows the wrong data, you look at the view (step 4) — your code. When request.user is missing
or a security header is wrong, you look at middleware (steps 2 and 5). When you cannot even reach
Django, you look at the server/WSGI layer (step 1). The rest of this course fills in what happens
inside step 4 — models, templates, forms — but this pipeline is the frame that holds all of it, and
knowing where your code sits is what turns "it doesn't work" into "I know which step to check".
Check your work
The one-sentence model. A Django app is a function: HTTP request in, HTTP response out; the machinery routes the request to your view.
The six steps, in order. Server/WSGI → middleware (in) → URL resolution → your view → middleware (out) → response.
Where your code sits. In the view (step 4) and the template it renders — almost everything you write is there.
What middleware does. Processes every request on the way in and every response on the way out — it is
why request.user and the session already exist when your view runs.
What URL resolution does. Matches the request path against patterns top-to-bottom to pick the view and
capture parameters; include delegates a path prefix to an app's own urls.py.
The request/response contract. The view reads from HttpRequest and must return an HttpResponse
(usually via render/redirect); returning None is the classic "didn't return an HttpResponse" error.
Practice
- Build the three files above in the
nidaanproject and load the root URL; confirm you see the text. - Add a second view
aboutat pathabout/returning different text; visit both. You have added a step-3 match and a step-4 view. - Visit a URL that matches no pattern (e.g.
/nope/) and read Django's 404 page — it even lists the patterns it tried. That is URL resolution failing, visibly. - In a view, return
Noneinstead of anHttpResponseand read the exact error; then fix it. Now the contract is not abstract. - In a view,
print(request.method, request.path, request.user)and watch the values in the runserver console — theHttpRequestis concrete data. - Draw the six-step pipeline from memory and label where each of your two views runs.
Official documentation
- Django — URL dispatcher — How
path,includeand resolution work. - Django — Request and response objects — Everything on
HttpRequestandHttpResponse. - Django — Middleware — The layers that wrap every request.
Next: apps, settings and project structure.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship