Build a complete REST API with Django REST Framework. Covers serializers, viewsets, authentication, pagination, and filtering.
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.
A REST API allows different applications to communicate over HTTP using standard methods:
| Method | Action | Example URL | Description |
|---|---|---|---|
GET | Read | /api/books/ | List all books |
GET | Read | /api/books/1/ | Get book with ID 1 |
POST | Create | /api/books/ | Create a new book |
PUT | Update | /api/books/1/ | Update entire book |
PATCH | Partial Update | /api/books/1/ | Update specific fields |
DELETE | Delete | /api/books/1/ | Delete the book |
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',
],
}
# 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
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
create() and update() methods. You can override any field or add custom validation.
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']
# 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:
| URL | Method | Action |
|---|---|---|
| /api/books/ | GET | List all books |
| /api/books/ | POST | Create a book |
| /api/books/1/ | GET | Retrieve book 1 |
| /api/books/1/ | PUT | Update book 1 |
| /api/books/1/ | PATCH | Partial update book 1 |
| /api/books/1/ | DELETE | Delete book 1 |
| /api/authors/ | GET | List all authors |
| /api/authors/1/ | GET | Retrieve author 1 |
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"
# 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"
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})
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
}
]
}
/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.
You now have a fully functional REST API with: