REST API Intermediate

Build a REST API with Django REST Framework

Build a complete REST API with Django REST Framework. Covers serializers, viewsets, authentication, pagination, and filtering.

DjangoZen Team Mar 29, 2026 5 min read 27 views

Django REST Framework (DRF) is the most popular toolkit for building Web APIs with Django. In this tutorial, you'll build a complete REST API for a bookstore, covering serializers, viewsets, authentication, filtering, and pagination.

What is a REST API?

A REST API allows different applications to communicate over HTTP using standard methods:

MethodActionExample URLDescription
GETRead/api/books/List all books
GETRead/api/books/1/Get book with ID 1
POSTCreate/api/books/Create a new book
PUTUpdate/api/books/1/Update entire book
PATCHPartial Update/api/books/1/Update specific fields
DELETEDelete/api/books/1/Delete the book

Step 1: Install and Configure DRF

pip install djangorestframework
# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'books',
]

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ],
}

Step 2: Create Models

# books/models.py
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    bio = models.TextField(blank=True)
    born_date = models.DateField(null=True, blank=True)

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=300)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    isbn = models.CharField(max_length=13, unique=True)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    published_date = models.DateField()
    description = models.TextField(blank=True)
    in_stock = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

Step 3: Create Serializers

Serializers convert Django model instances to JSON and validate incoming data:

# books/serializers.py
from rest_framework import serializers
from .models import Author, Book


class AuthorSerializer(serializers.ModelSerializer):
    books_count = serializers.IntegerField(source='books.count', read_only=True)

    class Meta:
        model = Author
        fields = ['id', 'name', 'bio', 'born_date', 'books_count']


class BookSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source='author.name', read_only=True)

    class Meta:
        model = Book
        fields = ['id', 'title', 'author', 'author_name', 'isbn',
                  'price', 'published_date', 'description',
                  'in_stock', 'created_at']

    def validate_isbn(self, value):
        if len(value) not in [10, 13]:
            raise serializers.ValidationError("ISBN must be 10 or 13 characters")
        return value
ModelSerializer automatically generates fields based on your model. It also creates create() and update() methods. You can override any field or add custom validation.

Step 4: Create ViewSets

ViewSets combine the logic for multiple views (list, create, retrieve, update, delete) into one class:

# books/views.py
from rest_framework import viewsets, filters
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
from .models import Author, Book
from .serializers import AuthorSerializer, BookSerializer


class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.all()
    serializer_class = AuthorSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = [filters.SearchFilter]
    search_fields = ['name']


class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.select_related('author').all()
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['author', 'in_stock']
    search_fields = ['title', 'description']
    ordering_fields = ['price', 'published_date', 'title']
    ordering = ['-created_at']

Step 5: Wire Up URLs with a Router

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

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

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

This automatically creates all these endpoints:

URLMethodAction
/api/books/GETList all books
/api/books/POSTCreate a book
/api/books/1/GETRetrieve book 1
/api/books/1/PUTUpdate book 1
/api/books/1/PATCHPartial update book 1
/api/books/1/DELETEDelete book 1
/api/authors/GETList all authors
/api/authors/1/GETRetrieve author 1

Step 6: Test Your API

DRF comes with a browsable API interface. Visit /api/ in your browser to see it. You can also test with curl:

# List all books
curl http://localhost:8000/api/books/

# Create a book (authenticated)
curl -X POST http://localhost:8000/api/books/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Token your-token-here" \
  -d '{"title": "Django for Pros", "author": 1, "isbn": "9781234567890", "price": "39.99", "published_date": "2025-01-15"}'

# Search books
curl http://localhost:8000/api/books/?search=django

# Filter by author and order by price
curl "http://localhost:8000/api/books/?author=1&ordering=price"

Step 7: Token Authentication

# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework.authtoken',
]

# Run migration
python manage.py migrate
# Create token for a user
python manage.py drf_createtoken username

# Or in code:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=user)
print(token.key)  # "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"

Step 8: Custom API Actions

Add custom endpoints to your viewsets:

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

class BookViewSet(viewsets.ModelViewSet):
    # ... existing code ...

    @action(detail=False, methods=['get'])
    def featured(self, request):
        # GET /api/books/featured/
        featured = self.queryset.filter(in_stock=True).order_by('-created_at')[:5]
        serializer = self.get_serializer(featured, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['post'])
    def toggle_stock(self, request, pk=None):
        # POST /api/books/1/toggle_stock/
        book = self.get_object()
        book.in_stock = not book.in_stock
        book.save()
        return Response({'in_stock': book.in_stock})

API Response Example

A typical JSON response from GET /api/books/:

{
    "count": 42,
    "next": "http://localhost:8000/api/books/?page=2",
    "previous": null,
    "results": [
        {
            "id": 1,
            "title": "Django for Professionals",
            "author": 1,
            "author_name": "William S. Vincent",
            "isbn": "9781735467221",
            "price": "39.99",
            "in_stock": true
        }
    ]
}
Pro Tip: DRF's browsable API at /api/ is one of the best features for development and debugging. It lets you test all endpoints directly from your browser without any additional tools.

Summary

You now have a fully functional REST API with:

  • Serializers for data conversion and validation
  • ViewSets for CRUD operations in minimal code
  • Routers for automatic URL configuration
  • Authentication with tokens
  • Filtering, searching, and ordering
  • Pagination for large datasets
  • Custom actions for non-standard endpoints
Ready to Build?

Skip the boilerplate. Get production-ready Django packages.

Browse Products