Jake Groszewski

Blog

Build a Django REST API with Django REST Framework: A Step-by-Step Tutorial

Build a Django REST API with Django REST Framework: A Step-by-Step Tutorial

Building a REST API in Django is one of the most common tasks Python developers tackle. Whether you're powering a React frontend, a mobile app, or a data dashboard, Django REST Framework (DRF) gives you a structured and reliable way to expose your data over HTTP.

This tutorial walks through building a working API from scratch, including models, serializers, views, URL routing, and basic token authentication.

Prerequisites

You should be comfortable with basic Django concepts: models, views, and URL configuration. You'll need Python 3.10+ and a virtual environment.

pip install django djangorestframework
django-admin startproject myapi .
python manage.py startapp books

Add both rest_framework and books to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    # Django defaults...
    'rest_framework',
    'books',
]

Step 1: Define Your Model

For this tutorial, we're building an API around a simple Book model.

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

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.CharField(max_length=255)
    published_year = models.IntegerField()
    isbn = models.CharField(max_length=13, unique=True)

    def __str__(self):
        return self.title

Run migrations:

python manage.py makemigrations
python manage.py migrate

Step 2: Create a Serializer

Serializers in DRF convert model instances to JSON (and back). A ModelSerializer does most of the heavy lifting automatically.

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

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'title', 'author', 'published_year', 'isbn']

You can add custom validation here too. For example, to restrict future publication years:

def validate_published_year(self, value):
    import datetime
    if value > datetime.date.today().year:
        raise serializers.ValidationError("Published year cannot be in the future.")
    return value

Step 3: Write API Views

DRF gives you several ways to write views. APIView offers the most control; ViewSets with Routers reduce boilerplate. We'll start with a ModelViewSet, which handles list, create, retrieve, update, and delete in one class.

# books/views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all().order_by('title')
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

IsAuthenticatedOrReadOnly means anyone can read the data, but only authenticated users can create, update, or delete records. That's a sensible default for public-facing APIs.

Step 4: Configure URL Routing

DRF's Router automatically generates URL patterns for a ViewSet.

# books/urls.py
from rest_framework.routers import DefaultRouter
from .views import BookViewSet

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

urlpatterns = router.urls

Then include the books URLs in your project's main urls.py:

# myapi/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('books.urls')),
    path('api-auth/', include('rest_framework.urls')),  # Adds login/logout to browsable API
]

At this point, GET /api/books/ returns a list of books, and POST /api/books/ creates a new one.

Step 5: Add Token Authentication

For most APIs, you'll want token-based authentication rather than session cookies. DRF includes a built-in TokenAuthentication system.

Add rest_framework.authtoken to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken',
]

Run migrations again:

python manage.py migrate

Update your DRF settings in settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
}

Add an endpoint to obtain a token:

# myapi/urls.py
from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    ...
    path('api/token/', obtain_auth_token, name='api_token_auth'),
]

Now POST a username and password to /api/token/ to receive an auth token:

curl -X POST http://127.0.0.1:8000/api/token/ \
  -d "username=admin&password=yourpassword"

Response:

{"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}

Include that token in subsequent requests:

curl -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" \
  http://127.0.0.1:8000/api/books/

Step 6: Test with the Browsable API

One of DRF's most useful features is the browsable API — an auto-generated HTML interface at any API endpoint. Start your dev server and visit http://127.0.0.1:8000/api/books/:

python manage.py runserver

You'll see a clean interface showing your data, available methods, and a form for POST requests. It's particularly useful during development and for onboarding teammates.

Step 7: Filter and Search

Real APIs need filtering. Install django-filter:

pip install django-filter

Add it to INSTALLED_APPS and update DRF settings:

REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
    ],
}

Update your ViewSet to enable filtering and search:

from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all().order_by('title')
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend, SearchFilter]
    filterset_fields = ['author', 'published_year']
    search_fields = ['title', 'author']

Now you can query like this:

GET /api/books/?author=Tolkien
GET /api/books/?search=ring

Actionable Takeaways

  • Start with ModelViewSet unless you need fine-grained control. It covers all CRUD operations with minimal code.
  • Use serializer validation to enforce business rules at the API boundary rather than in views.
  • Token authentication is a straightforward starting point; consider djangorestframework-simplejwt for JWT-based flows as your project grows.
  • django-filter is well worth adding early, as retrofitting filtering into an existing API is tedious.
  • The browsable API is a development convenience, but disable it in production by removing rest_framework.renderers.BrowsableAPIRenderer from your renderer classes if you don't want it exposed.

With these pieces in place: models, serializers, ViewSets, routing, and token auth, you have a production-worthy foundation to build on. From here, you can layer in pagination, throttling, nested serializers, or custom actions using DRF's @action decorator.