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)
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
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:- Add a Category model with name and slug fields, and run makemigrations and migrate
- Create a CategorySerializer and a CategoryViewSet
- Register categories on the router and confirm GET /api/categories/ returns your data
- Set permission_classes = [IsAuthenticated] on CategoryViewSet and confirm unauthenticated reads are now rejected
- Use the browsable API at /api/ to create, update, and delete a category with your token
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment