Routers & URLs

Difficulty: Advanced

DRF routers automatically generate URL patterns for ViewSets. Instead of manually defining URL patterns for each action (list, create, retrieve, update, delete), you register ViewSets with a router and it creates all the necessary URL patterns.

DRF provides two main routers: SimpleRouter and DefaultRouter. DefaultRouter extends SimpleRouter by adding a root API view that lists all registered endpoints. This root view provides a browsable API index at the base URL.

To use a router, create an instance, register ViewSets with URL prefixes, and include the router's URLs in your URL configuration. router.register('articles', ArticleViewSet) creates URL patterns for /articles/ (list/create) and /articles/{pk}/ (retrieve/update/delete).

Custom actions extend ViewSets with additional endpoints beyond standard CRUD. Use the @action decorator to define custom actions. By default, actions respond to POST requests and use the ViewSet's detail URL ({pk}/action-name/). Set detail=False for collection-level actions (/articles/action-name/).

The @action decorator accepts parameters like methods (HTTP methods), detail (True for instance-level, False for collection-level), url_path (custom URL segment), url_name (for URL reversing), and permission_classes (action-specific permissions).

Routers also support nested routing through third-party packages like drf-nested-routers, which creates URL patterns like /authors/{author_pk}/articles/ for nested resources.

Code examples

Router Configuration

# api/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('articles', views.ArticleViewSet)
router.register('categories', views.CategoryViewSet)
router.register('tags', views.TagViewSet, basename='tag')

urlpatterns = [
    path('api/', include(router.urls)),
]

# Generated URLs:
# GET/POST    /api/articles/         -> list/create
# GET/PUT/DELETE /api/articles/{pk}/  -> retrieve/update/destroy
# GET/POST    /api/categories/       -> list/create
# GET/PUT/DELETE /api/categories/{pk}/ -> retrieve/update/destroy
# GET         /api/tags/             -> list
# GET         /api/tags/{pk}/        -> retrieve
# GET         /api/                  -> API root (DefaultRouter only)

DefaultRouter auto-generates URL patterns from registered ViewSets. Use basename when the ViewSet does not have a queryset attribute. The API root view at /api/ lists all endpoints.

Custom Actions

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    # Detail action: POST /articles/{pk}/publish/
    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.is_published = True
        article.save()
        return Response({'status': 'Article published'})

    # Detail action: POST /articles/{pk}/archive/
    @action(detail=True, methods=['post'], url_path='archive')
    def archive(self, request, pk=None):
        article = self.get_object()
        article.status = 'archived'
        article.save()
        return Response({'status': 'Article archived'})

    # Collection action: GET /articles/featured/
    @action(detail=False, methods=['get'])
    def featured(self, request):
        featured = self.get_queryset().filter(is_featured=True)[:10]
        serializer = self.get_serializer(featured, many=True)
        return Response(serializer.data)

    # Collection action: GET /articles/stats/
    @action(detail=False, methods=['get'], url_path='stats')
    def statistics(self, request):
        qs = self.get_queryset()
        return Response({
            'total': qs.count(),
            'published': qs.filter(is_published=True).count(),
            'draft': qs.filter(is_published=False).count(),
        })

detail=True creates /articles/{pk}/action/. detail=False creates /articles/action/. url_path customizes the URL segment. self.get_object() gets the instance for detail actions. self.get_queryset() gets the queryset for collection actions.

Mixing Router and Manual URLs

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('articles', views.ArticleViewSet)
router.register('categories', views.CategoryViewSet)

urlpatterns = [
    # Router-generated URLs
    path('api/v1/', include(router.urls)),

    # Manual API URLs (for non-ViewSet views)
    path('api/v1/auth/login/', views.LoginAPIView.as_view(), name='api-login'),
    path('api/v1/auth/register/', views.RegisterAPIView.as_view(), name='api-register'),
    path('api/v1/search/', views.SearchAPIView.as_view(), name='api-search'),

    # DRF browsable API authentication
    path('api-auth/', include('rest_framework.urls')),
]

Router URLs and manual URL patterns can coexist. Use manual URLs for non-ViewSet views (like authentication endpoints). Include rest_framework.urls for the browsable API login/logout buttons.

API Versioning with Routers

# api/v1/urls.py
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('articles', views.ArticleViewSet)
router.register('users', views.UserViewSet)

urlpatterns = router.urls

# api/v2/urls.py
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('articles', views.ArticleV2ViewSet)
router.register('users', views.UserV2ViewSet)

urlpatterns = router.urls

# project/urls.py
from django.urls import path, include

urlpatterns = [
    path('api/v1/', include('api.v1.urls')),
    path('api/v2/', include('api.v2.urls')),
]

API versioning through URL prefixes uses separate routers for each version. Each version can have different ViewSets and serializers while maintaining backward compatibility.

Key points

Concepts covered

DefaultRouter, SimpleRouter, register, Custom Actions, action Decorator