RizTech Academy logo
RizTech Academy
Getting StartedLesson 3 of 425 min

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:

  1. The web server receives the request and hands it to Django through WSGI or ASGI (the wsgi.py/ asgi.py entry points from the setup lesson). In development, runserver plays the web server's role.
  2. 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 why request.user already exists by the time your view runs.
  3. URL resolution. Django takes the path /patients/ and matches it against your URL patterns (config/urls.py and the app urls.py files it includes), top to bottom, until one matches. The match determines which view function runs and captures any parameters from the URL.
  4. Your view runs. This is your code. The view receives an HttpRequest object and does the actual work — usually query the database via the ORM, then render a template with the data. It must return an HttpResponse.
  5. 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).
  6. 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 (the request parameter) carries everything about the incoming request: request.method ("GET", "POST", …), request.GET and request.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.
  • HttpResponse is what you must return. Usually you will not construct it by hand — you will use helpers like render(request, "template.html", context) (which builds an HttpResponse from a template) or redirect("home"). But under every one of those is an HttpResponse, 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

  1. Build the three files above in the nidaan project and load the root URL; confirm you see the text.
  2. Add a second view about at path about/ returning different text; visit both. You have added a step-3 match and a step-4 view.
  3. 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.
  4. In a view, return None instead of an HttpResponse and read the exact error; then fix it. Now the contract is not abstract.
  5. In a view, print(request.method, request.path, request.user) and watch the values in the runserver console — the HttpRequest is concrete data.
  6. Draw the six-step pipeline from memory and label where each of your two views runs.

Official documentation

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