Introduction
URL design is one of the most visible aspects of your API. Well-designed URLs are intuitive, consistent, and self-documenting. They tell developers exactly what resource they're working with before they even read the documentation.
Key Concepts
URL Pattern: A route definition that maps a URL path to a view function. Django uses path() and re_path() for pattern matching.
Path Converters: Built-in converters like <int:pk> that capture and convert URL segments to Python types.
URL Namespacing: Organizing URLs into logical groups with prefixes like /api/v1/ to avoid conflicts and enable versioning.
Trailing Slashes: Django's convention of using trailing slashes (/articles/) and the APPEND_SLASH setting.
Real World Context
Your URL structure directly impacts:
- Developer experience: Intuitive URLs reduce onboarding time
- API versioning: Clean paths like
/api/v1/vs/api/v2/enable smooth migrations - SEO and caching: Consistent URL patterns improve cacheability
- Documentation: Good URLs are self-documenting
Deep Dive
Basic API URL Configuration
Use include() to keep your API URLs in a separate file, then mount them under a prefix like api/ in the project-level configuration:
python# myproject/urls.py from django.urls import path, include urlpatterns = [ path('api/', include('myapp.api_urls')), ]
python# myapp/api_urls.py from django.urls import path from . import views urlpatterns = [ # Collection endpoints path('articles/', views.article_list, name='api-article-list'), path('users/', views.user_list, name='api-user-list'), # Detail endpoints with path converters path('articles/<int:pk>/', views.article_detail, name='api-article-detail'), path('users/<int:pk>/', views.user_detail, name='api-user-detail'), # Nested resources path('articles/<int:article_id>/comments/', views.article_comments, name='api-article-comments'), # Custom actions path('articles/<int:pk>/publish/', views.article_publish, name='api-article-publish'), ]
Naming each URL pattern (e.g., name='api-article-list') enables reverse lookups with reverse(), making your code resilient to URL changes.
Path Converters
Django provides several built-in path converters that capture URL segments and automatically convert them to the correct Python type:
python# Built-in converters path('articles/<int:pk>/', ...) # Integer: /articles/42/ path('articles/<slug:slug>/', ...) # Slug: /articles/my-post/ path('files/<path:filepath>/', ...) # Path with slashes: /files/docs/readme.txt path('users/<uuid:id>/', ...) # UUID: /users/550e8400-e29b-41d4.../ path('tags/<str:name>/', ...) # String (default): /tags/python/
The <int:pk> converter rejects non-integer values with a 404 automatically, saving you from manual type-checking in the view.
API Versioning with URLs
Separate versioned URL files let you evolve your API without breaking existing clients. Each version gets its own URL module and namespace:
python# myproject/urls.py urlpatterns = [ path('api/v1/', include('myapp.api.v1.urls')), path('api/v2/', include('myapp.api.v2.urls')), ]
python# myapp/api/v1/urls.py from django.urls import path from . import views app_name = 'api-v1' # Namespace urlpatterns = [ path('articles/', views.ArticleListView.as_view(), name='articles'), ]
The app_name attribute creates a namespace so you can reference URLs as api-v1:articles without colliding with other apps.
Using URL Names in Code
The reverse() function generates URLs from pattern names, so your code stays correct even when URL paths change:
pythonfrom django.urls import reverse # Generate URL from name url = reverse('api-article-detail', kwargs={'pk': 42}) # Result: '/api/articles/42/' # In responses (HATEOAS) def article_detail(request, pk): article = get_object_or_404(Article, pk=pk) return JsonResponse({ 'id': article.id, 'title': article.title, 'links': { 'self': request.build_absolute_uri(), 'comments': reverse('api-article-comments', kwargs={'article_id': pk}), } })
Using reverse() instead of hardcoded strings ensures your links stay valid as URL patterns evolve. The request.build_absolute_uri() helper generates a full URL including the domain.
RESTful URL Patterns
REST conventions dictate using plural nouns for resource collections and relying on HTTP methods for actions, not verb-based URLs:
python# Following REST conventions urlpatterns = [ # Resource collections (plural nouns) path('articles/', views.articles, name='articles'), # GET=list, POST=create path('articles/<int:pk>/', views.article, name='article'), # GET, PUT, PATCH, DELETE # Nested resources path('articles/<int:pk>/comments/', views.comments), path('users/<int:pk>/articles/', views.user_articles), # Avoid: action-based URLs # path('createArticle/', ...) # BAD # path('getArticles/', ...) # BAD ]
The commented-out BAD examples show the anti-pattern of embedding actions in URLs. Instead, let GET /articles/ handle listing and POST /articles/ handle creation.
Common Pitfalls
-
Using verbs in URLs:
/api/getArticles/should beGET /api/articles/. Let HTTP methods express the action. -
Inconsistent pluralization: Mixing
/article/and/users/. Pick one convention (plural is standard) and stick with it. -
Deep nesting:
/users/1/posts/5/comments/3/likes/is hard to work with. Limit nesting to 2 levels max.
Best Practices
- Use plural nouns:
/articles/not/article/ - Use hyphens for multi-word:
/user-profiles/not/userProfiles/ - Keep URLs lowercase:
/api/articles/not/API/Articles/ - Version from the start:
/api/v1/even if you don't plan v2 yet - Name all URL patterns: Enables
reverse()and easier maintenance - Use namespaces:
app_name = 'api'prevents naming collisions
Summary
Django's URL routing maps HTTP requests to view functions using path() patterns. Design RESTful URLs using plural nouns for resources, path converters for dynamic segments, and namespaces for organization. Always name your URL patterns to enable reverse URL lookups. Keep URLs consistent, lowercase, and limit nesting depth for maintainable APIs.
Code Examples
from django.urls import path, include
# myapp/api_urls.py
urlpatterns = [
path('articles/', views.article_list, name='api-article-list'),
path('articles/<int:pk>/', views.article_detail, name='api-article-detail'),
path('articles/<int:article_id>/comments/', views.article_comments),
]
# project/urls.py
urlpatterns = [
path('api/v1/', include('myapp.api_urls')),
]