~/TechPurAI
~/tutorials/django-from-scratch/urls-and-views
beginner·part 3 of 22·3 min read

URLs and views: routing a request to a response

Updated Aug 31, 2026Python · Django

Every request Django handles goes through the same two steps: a URL pattern matches the request path, and the view function attached to it runs and returns a response. This part wires that up for the blog's homepage, the pattern every other route in this project follows.

A minimal view

python
# blog/views.py
from django.http import HttpResponse

def post_list(request):
    return HttpResponse("Blog posts will go here.")

Every view's first parameter is the HttpRequest object, always — even a view that ignores it entirely still has to accept it. Whatever it returns has to be an HttpResponse (or a subclass of one, which render() — coming in part 4 — actually returns).

The app's own urls.py

python
# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.post_list, name="post_list"),
]

blog/urls.py isn't generated by startapp — creating it by hand, per app, is the standard pattern, because it's what lets each app own its own routes independently of the project's root URLconf.

Connecting it to the project

python
# blogsite/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
]

include("blog.urls") is the link between the project-level router and the app's own routes — without this line, blog/urls.py exists but Django never looks at it. The empty string as the first argument to path() means blog.urls handles everything at the site root; a real project with multiple apps might mount one at path("blog/", include("blog.urls")) instead, prefixing every route inside it.

bash
python manage.py runserver

Visiting http://127.0.0.1:8000/ now runs post_list and shows the placeholder text — proof the full chain (browser → root URLconf → app URLconf → view → response) works end to end.

Common mistake

Adding a route to blog/urls.py but forgetting the matching include() line back in blogsite/urls.py — the new route 404s, and it's easy to assume the typo is in the new code when it's actually a missing line in a file you didn't touch.

FAQ

Is this "function-based view" the only kind Django has? No — Django also has class-based views (ListView, DetailView, and others), which this series switches to starting in part 9 once the patterns repeat enough to benefit from them. Function-based views like post_list here are the more explicit, more transparent starting point for understanding what a class-based view does underneath.

Can a URL pattern capture part of the path, like a post's slug? Yes — path("posts/<slug:post_slug>/", views.post_detail) captures a URL segment as a converter-typed argument (slug, int, str, and others), passed to the view as a matching keyword argument. This series introduces that once there's a real Post model with a slug field to match against.

What if two URL patterns could both match the same path? Django checks patterns in the order they're listed and uses the first match — a more specific pattern needs to come before a more general one that would otherwise catch it first.

Next: returning actual HTML instead of a raw string, with Django's template engine.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous2. Creating your first Django appnext →4. Rendering templates: connecting a view to HTML