In the previous lesson you built the data foundation: Post, Author, Tag, and Comment models. Now it is time to expose that data to the world. This lesson shows you how to use Django REST Framework (DRF) to turn those models into a RESTful API with full CRUD support. You will learn serializers, viewsets, routers, token authentication, and permission controls - the exact building blocks that power modern API backends for React and Vue frontends.

1. Learning Objectives

By the end of this lesson, you will be able to:
  • Install and configure Django REST Framework (DRF) in an existing Django project
  • Convert Django models into serializers with ModelSerializer
  • Build full CRUD endpoints using ModelViewSet and a DefaultRouter
  • Secure your API with token-based authentication
  • Control access to endpoints with DRF permission classes
  • Test every endpoint with curl commands

2. Why This Matters

Modern web applications rarely render HTML on the server anymore. Instead, a backend exposes a RESTful API, and a React, Vue, mobile, or third-party frontend consumes it. In the previous lesson you designed the blog's data foundation: Post, Author, Tag, and Comment models. This lesson turns those models into a real API that any frontend can use. For a DevOps engineer, knowing DRF means you can debug API misconfigurations, design endpoints for internal tooling, and deploy API backends with confidence.

3. Core Concepts

Django REST Framework is a powerful toolkit for building Web APIs on top of Django. Four building blocks do most of the work:
  • Serializer - converts model instances into JSON (and back), with built-in validation
  • ViewSet - a class that groups list, create, retrieve, update, and delete actions for one model
  • Router - automatically generates URL patterns from a ViewSet, so you never hand-write CRUD routes
  • Authentication and Permissions - authentication verifies who you are (token auth), permissions decide what you may do (read-only vs read-write)
Together these give you a complete, browsable REST API with just a few dozen lines of code.

4. Hands-On Practice: Building a REST API for the Blog

We will build on the models from the previous lesson. By the end of this section you will have a browsable, testable API at /api/ with full CRUD support for posts, authors, tags, and comments.

4.1. Install Django REST Framework

Install DRF with pip, then register it in your project settings. The authtoken app is required for token authentication.
pip install djangorestframework
Install Django REST Framework
kubectl get pods -w
$ pip install djangorestframework
Collecting djangorestframework
Downloading djangorestframework-3.15.2-py3-none-any.whl (1.1 MB)
Successfully installed djangorestframework-3.15.2
Pip install output
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    # ... existing apps ...
    'rest_framework',
    'rest_framework.authtoken',
    'blog',
]
settings.py - register DRF apps

4.2. Create Serializers

Create a serializers.py file in your blog app. ModelSerializer automatically generates fields and validation from the model definition.
from rest_framework import serializers
from .models import Author, Tag, Post, Comment

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name', 'bio']

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ['id', 'name']

class PostSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)
    tags = TagSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ['id', 'title', 'slug', 'body', 'author', 'tags', 'created_at']

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ['id', 'post', 'author_name', 'content', 'created_at']
blog/serializers.py - ModelSerializer for each model

4.3. Build ViewSets

Create the views. ModelViewSet provides list, create, retrieve, update, partial_update, and destroy actions out of the box.
from rest_framework import viewsets
from .models import Post, Author, Tag, Comment
from .serializers import PostSerializer, AuthorSerializer, TagSerializer, CommentSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all().order_by('-created_at')
    serializer_class = PostSerializer

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.all()
    serializer_class = AuthorSerializer

class TagViewSet(viewsets.ModelViewSet):
    queryset = Tag.objects.all()
    serializer_class = TagSerializer

class CommentViewSet(viewsets.ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
blog/views.py - one ViewSet per model

4.4. Register Routes with a Router

Wire the viewsets to URLs. DefaultRouter generates the standard CRUD URL patterns, including a browsable API root.
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('posts', views.PostViewSet)
router.register('authors', views.AuthorViewSet)
router.register('tags', views.TagViewSet)
router.register('comments', views.CommentViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]
blog/urls.py - router registration
Then include the blog URLs from the project-level urls.py so /api/ is reachable.

4.5. Add Token Authentication

Configure TokenAuthentication as the default, then run migrate to create the authtoken tables. Generate a token for your user.
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
}
settings.py - token authentication
python manage.py migrate
Apply the authtoken migration
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model

user = get_user_model().objects.get(username='gataya')
token, created = Token.objects.get_or_create(user=user)
print(token.key)
Generate a token in the Django shell

4.6. Add Permissions

Permissions control what an authenticated user is allowed to do. IsAuthenticatedOrReadOnly lets anyone read, but requires a valid token for write operations. Set it as the default, or override it per-viewset.
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
}
settings.py - default permission classes

4.7. Test the API with curl

Start the dev server and exercise the API. Reads are public; writes require the token in the Authorization header.
kubectl get pods -w
$ python manage.py runserver
Starting development server at http://127.0.0.1:8000/
Start the development server
kubectl get pods -w
$ curl -s http://127.0.0.1:8000/api/posts/
[{"id": 1, "title": "My First Post", "slug": "my-first-post", ...}]
GET - list posts (public)
kubectl get pods -w
$ curl -s -X POST http://127.0.0.1:8000/api/posts/ \
-H "Content-Type: application/json" \
-d '{"title": "Hacked"}'
{"detail": "Authentication credentials were not provided."}
POST without a token is rejected
kubectl get pods -w
$ curl -s -X POST http://127.0.0.1:8000/api/posts/ \
-H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" \
-H "Content-Type: application/json" \
-d '{"title": "Hello API", "body": "First API post"}'
{"id": 8, "title": "Hello API", "slug": "hello-api", ...}
POST with a valid token succeeds

5. Common Errors & Solutions

  • ModuleNotFoundError: No module named 'rest_framework' - DRF is not installed. Run pip install djangorestframework, add both DRF apps to INSTALLED_APPS, and run migrate.
  • AttributeError: 'QuerySet' object has no attribute 'data' - serializer.data only exists on a serializer instance. Pass many=True when serializing a queryset, or serialize one instance at a time.
  • 400 Bad Request: This field is required - the serializer never received your data. Always call serializer.is_valid() and pass data=request.data to the serializer constructor before calling .save().
  • 403 Forbidden: PermissionDenied - the permission class blocked the request. Send a valid Authorization: Token [YOUR_TOKEN] header, or relax the permission to IsAuthenticatedOrReadOnly for public reads.
  • 404 at /api/posts/ - the router was never mounted. Confirm router.register(...) for each viewset and path('api/', include(router.urls)) in urlpatterns.
  • 401 Unauthorized even with a token - the authtoken table does not exist yet, or the header is malformed. Run python manage.py migrate and send the header exactly as Authorization: Token [YOUR_TOKEN] with no extra spaces.

6. Summary Checklist

  • rest_framework and rest_framework.authtoken are in INSTALLED_APPS and migrated
  • A ModelSerializer exists for each model you want to expose
  • Each model has a ModelViewSet with a queryset and serializer_class
  • All viewsets are registered on a DefaultRouter and included in urlpatterns
  • TokenAuthentication is configured and you generated a token for your user
  • IsAuthenticatedOrReadOnly (or stricter) permissions are applied
  • POST with a token succeeds, and POST without a token returns 401

7. Practice Exercise

Extend the blog API on your own:
  1. Add a Category model with name and slug fields, and run makemigrations and migrate
  2. Create a CategorySerializer and a CategoryViewSet
  3. Register categories on the router and confirm GET /api/categories/ returns your data
  4. Set permission_classes = [IsAuthenticated] on CategoryViewSet and confirm unauthenticated reads are now rejected
  5. Use the browsable API at /api/ to create, update, and delete a category with your token

8. Next Steps

You now have a working REST API for your blog models with serializers, viewsets, routing, token authentication, and permissions. The next lesson covers Django API testing and authentication deep-dive: writing automated tests for your endpoints with DRF's APITestCase, and moving from token auth to JSON Web Tokens (JWT) with djangorestframework-simplejwt for stateless, scalable authentication. We will also add pagination, filtering, and rate limiting so your API is production-ready.

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen August 7, 2026

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment