Skip to main content

Django REST Routers

Introduction

When building APIs with Django REST Framework (DRF), one of the most powerful features at your disposal is the router system. Routers provide a simple, quick, and consistent way to wire up your ViewSet-based API logic to a set of URLs. If you've been manually defining URL patterns for each of your API endpoints, routers will help you automate this process and follow REST conventions more easily.

In this tutorial, we'll explore Django REST Framework routers, understand their benefits, and learn how to implement them in your Django applications. By the end, you'll be able to create clean, RESTful API endpoints with minimal code.

What are DRF Routers?

Routers in Django REST Framework are classes that automatically determine the URL configuration for your API views. They work with ViewSets to create a standardized URL structure for your resources, following RESTful principles.

The main advantages of using routers include:

  1. Consistency - Ensures all your API endpoints follow the same URL pattern
  2. Reduced Boilerplate - Automatically generates URL patterns for common operations
  3. REST Compliance - Enforces RESTful URL design and HTTP method usage
  4. Relationship Handling - Simplifies defining nested resources and relationships

Basic Router Setup

Let's start with a simple example. Imagine we have a book management API with a BookViewSet:

python
# views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer

Without routers, we would need to define URL patterns for listing, creating, retrieving, updating, and deleting books manually. With routers, the process becomes much simpler:

python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import BookViewSet

router = DefaultRouter()
router.register('books', BookViewSet)

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

With just these few lines, the router automatically generates the following URL patterns:

  • GET /api/books/ - List all books
  • POST /api/books/ - Create a new book
  • GET /api/books/{id}/ - Retrieve a specific book
  • PUT /api/books/{id}/ - Update a specific book
  • PATCH /api/books/{id}/ - Partially update a specific book
  • DELETE /api/books/{id}/ - Delete a specific book

Types of Routers

Django REST Framework provides two main router classes:

1. SimpleRouter

The SimpleRouter class is a basic router that creates standard URL patterns for your ViewSets:

python
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('books', BookViewSet)

The SimpleRouter generates URLs for list, detail, and the standard HTTP methods, but doesn't include the root API view.

2. DefaultRouter

The DefaultRouter extends the SimpleRouter and adds a default API root view that lists all your registered ViewSets:

python
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('books', BookViewSet)
router.register('authors', AuthorViewSet)

When you navigate to the API root (e.g., /api/), the DefaultRouter provides a browsable HTML interface showing all available endpoints:

json
{
"books": "http://localhost:8000/api/books/",
"authors": "http://localhost:8000/api/authors/"
}

This makes it easy for API consumers to discover available resources.

Custom Methods and Actions

ViewSets can define custom actions beyond the standard CRUD operations. Routers can map these actions to appropriate URLs using the @action decorator:

python
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer

@action(detail=True, methods=['get'])
def chapters(self, request, pk=None):
book = self.get_object()
chapters = book.chapter_set.all()
serializer = ChapterSerializer(chapters, many=True)
return Response(serializer.data)

@action(detail=False, methods=['get'])
def recent(self, request):
recent_books = Book.objects.all().order_by('-publication_date')[:5]
serializer = self.get_serializer(recent_books, many=True)
return Response(serializer.data)

The router will automatically create the following additional URLs:

  • GET /api/books/{id}/chapters/ - Get chapters for a specific book
  • GET /api/books/recent/ - Get the 5 most recently published books

The detail=True parameter indicates that the action relates to a single object (requiring a primary key in the URL), while detail=False indicates an action on the entire collection.

Nested Resources

A common pattern in RESTful APIs is to represent relationships between resources using nested URLs. While DRF routers don't directly support this pattern, we can achieve it by creating nested routers:

python
# Requires the drf-nested-routers package
pip install drf-nested-routers
python
from rest_framework_nested import routers
from .views import BookViewSet, ChapterViewSet

router = DefaultRouter()
router.register('books', BookViewSet)

# Create a nested router for chapters under books
chapters_router = routers.NestedSimpleRouter(router, 'books', lookup='book')
chapters_router.register('chapters', ChapterViewSet)

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

This creates URLs like:

  • GET /api/books/{book_id}/chapters/ - List chapters for a specific book
  • POST /api/books/{book_id}/chapters/ - Create a new chapter for a specific book
  • GET /api/books/{book_id}/chapters/{chapter_id}/ - Get a specific chapter of a book

Router URL Configuration Options

When registering a ViewSet with a router, you can customize the URL patterns with additional parameters:

python
router.register('books', BookViewSet, basename='book')

The available parameters include:

  • basename - Used for the URL names when reversing URLs in the viewset
  • lookup_value_regex - A string representing a regular expression that will be used for the path segment matching the viewset's lookup field

Example with custom lookup regex:

python
router.register('books', BookViewSet, basename='book', lookup_value_regex=r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')

This would match UUIDs in the URL rather than the default which matches numeric IDs.

Real-World Example: Blog API

Let's put everything together in a real-world example for a blog API:

python
# models.py
from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
categories = models.ManyToManyField(Category, related_name='posts')

class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
python
# serializers.py
from rest_framework import serializers
from .models import Category, Post, Comment
from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email']

class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug']

class CommentSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)

class Meta:
model = Comment
fields = ['id', 'text', 'author', 'created_at']

class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
categories = CategorySerializer(many=True, read_only=True)
comment_count = serializers.IntegerField(source='comments.count', read_only=True)

class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'categories',
'created_at', 'updated_at', 'comment_count']
python
# views.py
from rest_framework import viewsets, permissions
from .models import Category, Post, Comment
from .serializers import CategorySerializer, PostSerializer, CommentSerializer
from rest_framework.decorators import action
from rest_framework.response import Response

class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'slug'

class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer

def perform_create(self, serializer):
serializer.save(author=self.request.user)

@action(detail=True, methods=['get'])
def comments(self, request, pk=None):
post = self.get_object()
comments = post.comments.all()
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)

@action(detail=False, methods=['get'])
def trending(self, request):
# In a real app, you might use analytics data here
trending_posts = Post.objects.all().order_by('-created_at')[:5]
serializer = self.get_serializer(trending_posts, many=True)
return Response(serializer.data)

class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer

def perform_create(self, serializer):
post_id = self.kwargs.get('post_pk')
serializer.save(author=self.request.user, post_id=post_id)
python
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_nested import routers
from .views import CategoryViewSet, PostViewSet, CommentViewSet

router = DefaultRouter()
router.register('categories', CategoryViewSet)
router.register('posts', PostViewSet)

# Create nested router for comments
posts_router = routers.NestedSimpleRouter(router, 'posts', lookup='post')
posts_router.register('comments', CommentViewSet, basename='post-comments')

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

With this setup, we have created a complete blog API with the following endpoints:

  • /api/categories/ - List all categories
  • /api/categories/{slug}/ - Get a category by slug
  • /api/posts/ - List or create posts
  • /api/posts/{id}/ - Retrieve, update or delete a post
  • /api/posts/trending/ - Get trending posts
  • /api/posts/{id}/comments/ - View comments for a specific post
  • /api/posts/{id}/comments/{comment_id}/ - Manage specific comments

Summary

Django REST Framework routers provide a powerful way to simplify the creation of consistent, RESTful API endpoints. By using routers together with ViewSets:

  1. You save time by automating URL configuration
  2. You ensure consistency across your API
  3. You follow REST conventions by default
  4. You gain browsable API documentation for free

Key concepts we covered:

  • Setting up basic routers with SimpleRouter and DefaultRouter
  • Adding custom actions to ViewSets with the @action decorator
  • Handling nested resources with nested routers
  • Creating a complete API with proper resource relationships

Additional Resources and Exercises

Resources

Exercises

  1. Basic Router Practice: Create a simple library API with Book and Author resources using DRF routers.

  2. Custom Actions: Add custom actions to a ProductViewSet that allow filtering by "on_sale" and "featured" products.

  3. Nested Resources: Build a task management API with Projects and Tasks, where Tasks are nested under Projects.

  4. Advanced Router Configuration: Create an e-commerce API with custom URL patterns for special operations like "add_to_cart" and "checkout".

  5. API Versioning with Routers: Implement versioning for your API by setting up multiple routers for different API versions.

By mastering Django REST Framework routers, you'll be able to create clean, consistent APIs that follow best practices with minimal effort, allowing you to focus on your application's business logic rather than URL configuration details.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)