Introduction
Django REST Framework (DRF) is the industry-standard toolkit for building JSON/XML APIs on Django. Serializers handle data transformation and validation, Views/ViewSets handle HTTP semantics, and permission/throttle layers manage security.
In this article we build a complete blog API from basic Serializer to ModelViewSet. We cover nested serializers, custom validation, and pagination as real-world requirements.
Serializer Fundamentals
A Serializer converts Python objects to JSON and back. is_valid() runs validation; validated_data holds clean input. read_only and write_only fields clarify the API contract.
- serializer.data: serialized output
- serializer.save(): create/update hooks
- to_representation() for custom output format
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
body = serializers.CharField(max_length=1000)
def validate_body(self, value):
if len(value.strip()) < 10:
raise serializers.ValidationError('Comment must be at least 10 characters.')
return valueModelSerializer and Relations
ModelSerializer auto-generates fields from Model metadata. PrimaryKeyRelatedField and nested serializers expose ForeignKey/ManyToMany relations. The depth parameter helps prototyping but loses control in production.
class PostSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
tags = serializers.SlugRelatedField(
many=True, slug_field='name', queryset=Tag.objects.all()
)
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'body', 'author_name', 'tags']
read_only_fields = ['id', 'slug']APIView and Generic Views
APIView provides separate handlers per HTTP method. Generic views offer patterns like ListCreateAPIView. Remember select_related on querysets for performance.
from rest_framework import generics, permissions
class PostListCreateAPIView(generics.ListCreateAPIView):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return Post.objects.select_related('author').prefetch_related('tags')Define queryset in the view; serializer owns shape, view owns access rules.
ViewSet and Router
ViewSet bundles CRUD actions in one class: list, retrieve, create, update, destroy. DefaultRouter generates URLs automatically. The @action decorator adds custom endpoints.
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.select_related('author')
serializer_class = PostSerializer
lookup_field = 'slug'
@action(detail=True, methods=['post'])
def publish(self, request, slug=None):
post = self.get_object()
post.publish()
return Response({'status': 'published'})Permission, Pagination, and Filtering
IsAuthenticated, DjangoModelPermissions, and custom permission classes control access. PageNumberPagination standardizes paging. django-filter adds query param filtering.
- permission_classes at view or ViewSet level
- throttle_classes for rate limiting
- renderer_classes for non-JSON formats
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
],
}Error Handling and Testing
DRF returns consistent error format: {'field': ['message']}. Write integration tests with APIClient. Simulate auth with force_authenticate.
- Serializer unit tests: is_valid() and validate_* methods
- APITestCase for HTTP status and payload
- Factory pattern for test data
from rest_framework.test import APITestCase
class PostAPITest(APITestCase):
def test_create_requires_auth(self):
response = self.client.post('/api/posts/', {'title': 'Test'})
self.assertEqual(response.status_code, 401)Conclusion
DRF lets you build scalable services without breaking Django's ORM and auth stack. ViewSet + Router speeds CRUD projects; choose APIView or custom actions for complex workflows.
- Watch N+1 with nested serializers (prefetch_related)
- Versioning via namespace or URL prefix
- OpenAPI schema: use drf-spectacular