URL Routing

The taskflow app exists and is registered, but Django has no idea yet which web addresses should reach it — that's the job of URL routing. In this lesson we connect the project's root routing file to the taskflow app's own routes, so visiting a URL like /tasks/ actually reaches TaskFlow's code.

The Project's Root urls.py

Recall from Project Structure Deep Dive that ROOT_URLCONF points Django at taskflow_project/urls.py for every incoming request. Fresh out of startproject, that file looks like this:

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

Every entry in the urlpatterns list matches an incoming URL against a pattern and, on a match, hands the request off to something — here, Django's built-in admin site. We need to add an entry that sends anything starting with tasks/ into the taskflow app.

Delegating to an App with include()

Rather than listing every one of TaskFlow's individual routes directly inside the project's urls.py, Django's include() function lets us delegate an entire prefix to the app's own URL configuration file:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('tasks/', include('taskflow.urls')),
]

Reading this line by line: any URL beginning with tasks/ gets handed off to whatever urlpatterns are defined inside taskflow/urls.py, with the tasks/ prefix already consumed. This keeps the project's root file short and stable even as TaskFlow grows dozens of routes, and it keeps every route related to tasks physically grouped inside the taskflow app itself, right next to the views that handle them.

Creating the App's Own urls.py

As mentioned in the previous lesson, startapp does not generate a urls.py file automatically — we create it ourselves. Add a new file at taskflow/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.task_list, name='task-list'),
]

This single entry says: an empty path (which, remember, sits after the tasks/ prefix already consumed by the project's urls.py — so the full URL is actually /tasks/) should be handled by a view function named task_list, imported from this app's own views.py. We'll write that task_list view itself in Function-Based Views, the lesson after next.

path() vs. re_path(): Why Modern Django Prefers path()

Older Django code (and some tutorials still floating around) route URLs using regular expressions via a function called re_path(). Modern Django favors path() for the overwhelming majority of routes, because it uses simple, readable converters (like <int:pk> or <str:slug>) instead of regex syntax. We'll use these converters to build routes like a single task's detail page in the very next lesson, Dynamic URLs & Parameters.

re_path() still exists and is still valid Django, reserved for the smaller set of cases that genuinely need full regular expression matching — an unusual URL shape that path()'s converters can't express. For TaskFlow, and for the vast majority of real projects, path() covers essentially everything you'll need.

Naming URL Patterns with name=

Notice the name='task-list' argument in the app's urls.py above. Giving a URL pattern a name lets the rest of the codebase — views, and especially templates — refer to that URL by name instead of hardcoding its actual path. This matters more than it might sound: if we ever changed TaskFlow's task list from /tasks/ to something like /tasks/all/, every template using a hardcoded href="/tasks/" link would silently break, while every template referencing it by the name task-list would keep working without a single change. We'll put this to direct use with the {% url %} template tag once we reach Django Template Language.

App Namespacing for Larger Projects

As a project grows to include multiple apps, two different apps could end up with a same-named URL pattern — both taskflow and a hypothetical billing app might each want a pattern named list. Django solves this with an app namespace, declared right inside the app's own urls.py:

from django.urls import path
from . import views

app_name = 'taskflow'

urlpatterns = [
    path('', views.task_list, name='task-list'),
]

With app_name set, the same URL is referenced elsewhere as taskflow:task-list, keeping it unambiguous no matter how many other apps TaskFlow eventually grows to include. For a single-app project this isn't strictly necessary, but it's a cheap habit to build early, and one you'll see used throughout larger real-world Django codebases.

Next up: Head to Dynamic URLs & Parameters to build routes that capture values straight out of the URL, like loading one specific task by its ID.