Django REST Framework: Introduction to Building REST APIs

REST (Representational State Transfer) APIs enable communication between client applications and backend servers through standardized HTTP methods providing data in JSON or XML formats consumed by web frontends, mobile apps, and third-party integrations. Modern application architectures increasingly separate frontend and backend systems with RESTful APIs acting as the contract between them enabling independent development, technology flexibility, and platform-agnostic interfaces. Django REST Framework (DRF) extends Django with powerful tools for building Web APIs including serializers for data transformation, viewsets for simplified endpoint creation, routers for automatic URL routing, authentication and permission systems, pagination, filtering, throttling, and browsable API interfaces for development and testing. This comprehensive guide explores Django REST Framework fundamentals including understanding REST principles and HTTP methods, installing and configuring DRF in Django projects, creating your first API endpoints with APIView and ViewSet classes, using serializers to convert Django models to JSON, implementing CRUD operations through RESTful conventions, configuring content negotiation for JSON and other formats, building browsable APIs for interactive testing, understanding DRF request and response objects, implementing nested relationships and hyperlinked APIs, adding API documentation, and best practices for API design. Mastering Django REST Framework enables building professional APIs powering mobile applications, single-page applications, microservices architectures, and third-party integrations throughout modern web development.
Understanding REST Principles
REST (Representational State Transfer) is an architectural style for distributed systems defining constraints for creating scalable web services. RESTful APIs use standard HTTP methods (GET, POST, PUT, PATCH, DELETE) with predictable URL structures organizing resources hierarchically. Each resource has a unique URI (Uniform Resource Identifier) accessed through these HTTP verbs where GET retrieves data, POST creates new resources, PUT replaces entire resources, PATCH updates partial resources, and DELETE removes resources. REST APIs are stateless meaning each request contains all necessary information without server-side session storage enabling scalability and simplicity. Resources are represented in standard formats typically JSON or XML with HTTP status codes indicating operation results like 200 for success, 201 for creation, 400 for bad requests, 404 for not found, and 500 for server errors. Understanding REST principles ensures APIs follow conventions making them intuitive for developers and compatible with standard HTTP clients.
| HTTP Method | Purpose | Example URL | Success Status |
|---|---|---|---|
| GET | Retrieve resource(s) | /api/articles/ or /api/articles/1/ | 200 OK |
| POST | Create new resource | /api/articles/ | 201 Created |
| PUT | Replace entire resource | /api/articles/1/ | 200 OK |
| PATCH | Partial update | /api/articles/1/ | 200 OK |
| DELETE | Remove resource | /api/articles/1/ | 204 No Content |
# REST API URL structure examples
# Collection endpoints (list/create)
GET /api/articles/ # List all articles
POST /api/articles/ # Create new article
# Detail endpoints (retrieve/update/delete)
GET /api/articles/1/ # Get article with ID 1
PUT /api/articles/1/ # Replace article 1 completely
PATCH /api/articles/1/ # Update specific fields of article 1
DELETE /api/articles/1/ # Delete article 1
# Nested resources
GET /api/articles/1/comments/ # List comments for article 1
POST /api/articles/1/comments/ # Create comment for article 1
GET /api/articles/1/comments/5/ # Get specific comment
# Filtering and pagination
GET /api/articles/?category=tech&page=2
GET /api/articles/?ordering=-created_at&limit=10
# Request/Response example
# POST /api/articles/
# Request body:
{
"title": "Django REST Framework Guide",
"content": "Learn to build REST APIs...",
"author": 1,
"published": true
}
# Response (201 Created):
{
"id": 42,
"title": "Django REST Framework Guide",
"content": "Learn to build REST APIs...",
"author": {
"id": 1,
"username": "john"
},
"published": true,
"created_at": "2026-01-23T14:30:00Z",
"url": "http://api.example.com/api/articles/42/"
}
# Error responses
# 400 Bad Request
{
"title": ["This field is required."],
"content": ["Ensure this field has at least 10 characters."]
}
# 404 Not Found
{
"detail": "Not found."
}
# 403 Forbidden
{
"detail": "You do not have permission to perform this action."
}Installing Django REST Framework
Django REST Framework is a third-party package requiring installation through pip and configuration in Django settings. Installation adds rest_framework to INSTALLED_APPS enabling DRF views, serializers, and utilities throughout the project. Configuration includes authentication classes, permission policies, pagination settings, default renderer formats, and browsable API options. Proper setup ensures consistent behavior across all API endpoints while the browsable API provides interactive documentation for development and testing.
# Install Django REST Framework
pip install djangorestframework
# settings.py - Add to INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # Add DRF
'myapp',
]
# DRF configuration
REST_FRAMEWORK = {
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
# Permissions
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
# Pagination
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
# Renderers (output formats)
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
# Parsers (input formats)
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
],
# Throttling
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day',
},
# Filtering
'DEFAULT_FILTER_BACKENDS': [
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
# Date/Time formatting
'DATETIME_FORMAT': '%Y-%m-%dT%H:%M:%SZ',
# Exception handling
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
# Test settings
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
}
# URLs configuration
# urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('myapp.api.urls')),
path('api-auth/', include('rest_framework.urls')), # Login/logout for browsable API
]Creating Your First API
Building a simple API demonstrates DRF fundamentals including models, serializers, views, and URL routing. A basic API requires a Django model representing data, a serializer converting models to JSON, views handling HTTP requests, and URL patterns routing requests to views. DRF provides multiple approaches for creating APIs with APIView offering low-level control, generic views simplifying common patterns, and ViewSets combining related operations into single classes. Understanding each approach enables choosing appropriate abstractions based on complexity and customization requirements.
# models.py - Define data model
from django.db import models
from django.contrib.auth.models import User
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles')
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
# serializers.py - Convert models to JSON
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'content', 'author', 'author_name', 'published', 'created_at', 'updated_at']
read_only_fields = ['id', 'created_at', 'updated_at']
# views.py - API Views
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleSerializer
# Function-based views with decorators
@api_view(['GET', 'POST'])
def article_list(request):
"""
List all articles or create a new article
"""
if request.method == 'GET':
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET', 'PUT', 'PATCH', 'DELETE'])
def article_detail(request, pk):
"""
Retrieve, update or delete an article
"""
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return Response({'detail': 'Not found.'}, status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method in ['PUT', 'PATCH']:
partial = request.method == 'PATCH'
serializer = ArticleSerializer(article, data=request.data, partial=partial)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# Class-based APIView
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.http import Http404
class ArticleListAPIView(APIView):
"""
List all articles or create new article
"""
def get(self, request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ArticleDetailAPIView(APIView):
"""
Retrieve, update or delete article
"""
def get_object(self, pk):
try:
return Article.objects.get(pk=pk)
except Article.DoesNotExist:
raise Http404
def get(self, request, pk):
article = self.get_object(pk)
serializer = ArticleSerializer(article)
return Response(serializer.data)
def put(self, request, pk):
article = self.get_object(pk)
serializer = ArticleSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
article = self.get_object(pk)
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# urls.py - URL routing
from django.urls import path
from . import views
urlpatterns = [
# Function-based views
path('articles/', views.article_list, name='article-list'),
path('articles/<int:pk>/', views.article_detail, name='article-detail'),
# Or class-based views
# path('articles/', views.ArticleListAPIView.as_view(), name='article-list'),
# path('articles/<int:pk>/', views.ArticleDetailAPIView.as_view(), name='article-detail'),
]Using Generic Views
DRF generic views reduce boilerplate code by providing pre-built classes for common API patterns. ListAPIView handles GET requests for collections, CreateAPIView processes POST requests creating resources, RetrieveAPIView fetches single resources, UpdateAPIView handles PUT and PATCH requests, DestroyAPIView processes DELETE requests, and combined views like ListCreateAPIView and RetrieveUpdateDestroyAPIView merge multiple operations. Generic views require only model, serializer_class, and optional queryset or permission_classes attributes eliminating repetitive code while maintaining full customization through method overriding.
# views.py - Using generic views
from rest_framework import generics
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Article
from .serializers import ArticleSerializer
# List and Create (combined)
class ArticleListCreateView(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
# Custom creation logic
serializer.save(author=self.request.user)
# Retrieve, Update, and Destroy (combined)
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# Separate generic views
class ArticleListView(generics.ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleCreateView(generics.CreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ArticleRetrieveView(generics.RetrieveAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleUpdateView(generics.UpdateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleDestroyView(generics.DestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# Customizing generic views
class PublishedArticleListView(generics.ListAPIView):
serializer_class = ArticleSerializer
def get_queryset(self):
# Custom queryset filtering
return Article.objects.filter(published=True)
class UserArticleListView(generics.ListAPIView):
serializer_class = ArticleSerializer
def get_queryset(self):
# Filter by URL parameter
user_id = self.kwargs['user_id']
return Article.objects.filter(author_id=user_id)
# Overriding methods
class CustomArticleListView(generics.ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
# Custom response format
return Response({
'count': queryset.count(),
'results': serializer.data
})
# urls.py - Generic view URLs
from django.urls import path
from . import views
urlpatterns = [
path('articles/', views.ArticleListCreateView.as_view(), name='article-list'),
path('articles/<int:pk>/', views.ArticleDetailView.as_view(), name='article-detail'),
path('articles/published/', views.PublishedArticleListView.as_view(), name='published-articles'),
path('users/<int:user_id>/articles/', views.UserArticleListView.as_view(), name='user-articles'),
]| Generic View | HTTP Methods | Purpose | Common Use Case |
|---|---|---|---|
| ListAPIView | GET | List collection | Read-only list of items |
| CreateAPIView | POST | Create resource | Form submission endpoint |
| RetrieveAPIView | GET | Retrieve single resource | Detail page |
| UpdateAPIView | PUT, PATCH | Update resource | Edit forms |
| DestroyAPIView | DELETE | Delete resource | Delete confirmation |
| ListCreateAPIView | GET, POST | List and create | Collection endpoint with creation |
| RetrieveUpdateDestroyAPIView | GET, PUT, PATCH, DELETE | Full detail operations | Complete resource management |
Testing Your API
Testing REST APIs ensures endpoints function correctly handling valid requests, rejecting invalid data, enforcing permissions, and returning proper status codes. DRF provides APIClient for testing APIs with methods for GET, POST, PUT, PATCH, DELETE requests supporting authentication, headers, and request data. Tests verify serialization correctness, permission enforcement, filtering logic, pagination behavior, and error handling. The browsable API provides manual testing interfaces while automated tests ensure regression prevention and continuous integration compatibility.
# tests.py - API Testing
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from django.contrib.auth.models import User
from .models import Article
class ArticleAPITestCase(APITestCase):
def setUp(self):
# Create test user
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
# Create test article
self.article = Article.objects.create(
title='Test Article',
content='Test content',
author=self.user,
published=True
)
# API client
self.client = APIClient()
def test_get_article_list(self):
"""Test retrieving article list"""
response = self.client.get('/api/articles/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['title'], 'Test Article')
def test_get_article_detail(self):
"""Test retrieving single article"""
response = self.client.get(f'/api/articles/{self.article.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['title'], 'Test Article')
def test_create_article_authenticated(self):
"""Test creating article when authenticated"""
self.client.force_authenticate(user=self.user)
data = {
'title': 'New Article',
'content': 'New content',
'published': False
}
response = self.client.post('/api/articles/', data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Article.objects.count(), 2)
self.assertEqual(response.data['title'], 'New Article')
def test_create_article_unauthenticated(self):
"""Test creating article when not authenticated"""
data = {
'title': 'New Article',
'content': 'New content'
}
response = self.client.post('/api/articles/', data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_update_article(self):
"""Test updating article"""
self.client.force_authenticate(user=self.user)
data = {'title': 'Updated Title'}
response = self.client.patch(f'/api/articles/{self.article.id}/', data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.article.refresh_from_db()
self.assertEqual(self.article.title, 'Updated Title')
def test_delete_article(self):
"""Test deleting article"""
self.client.force_authenticate(user=self.user)
response = self.client.delete(f'/api/articles/{self.article.id}/')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Article.objects.count(), 0)
def test_invalid_data(self):
"""Test validation errors"""
self.client.force_authenticate(user=self.user)
data = {'title': ''} # Empty title should fail
response = self.client.post('/api/articles/', data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('title', response.data)
def test_article_not_found(self):
"""Test 404 for non-existent article"""
response = self.client.get('/api/articles/999/')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# Running tests
# python manage.py test myapp.testsAPI Design Best Practices
- Use nouns for resources: Name endpoints after resources not actions like /api/articles/ not /api/get-articles/
- Implement proper HTTP methods: Use GET for retrieval, POST for creation, PUT/PATCH for updates, DELETE for removal following REST conventions
- Version your API: Include version in URLs like /api/v1/articles/ enabling breaking changes without affecting existing clients
- Return appropriate status codes: Use 200 for success, 201 for creation, 400 for validation errors, 404 for not found, 500 for server errors
- Implement pagination: Paginate large collections preventing memory issues and improving response times
- Provide filtering and sorting: Enable clients to filter and sort collections using query parameters
- Use consistent naming: Follow consistent naming conventions for fields, endpoints, and parameters across entire API
- Document thoroughly: Provide comprehensive API documentation including endpoints, parameters, request/response examples, and authentication requirements
- Handle errors gracefully: Return meaningful error messages with details helping developers debug issues
- Secure properly: Implement authentication, authorization, rate limiting, and HTTPS protecting API from abuse
Conclusion
Django REST Framework transforms Django into a powerful platform for building Web APIs providing comprehensive tools for serialization, views, authentication, permissions, and documentation. REST principles define architectural constraints creating scalable, stateless APIs using standard HTTP methods and status codes with resources organized hierarchically through predictable URLs. Installing and configuring DRF enables browsable APIs, authentication systems, pagination, throttling, and customizable renderers supporting JSON and other formats. Function-based views with api_view decorator offer simplicity while class-based APIView provides object-oriented structure and generic views eliminate boilerplate for common patterns. Serializers convert Django models to JSON representations handling validation, nested relationships, and custom fields transforming complex data structures into API-friendly formats. Generic views including ListAPIView, CreateAPIView, RetrieveAPIView, UpdateAPIView, and combined variants reduce code significantly requiring only model and serializer_class attributes. Testing APIs with APIClient ensures endpoints function correctly handling valid requests, rejecting invalid data, enforcing permissions, and returning appropriate status codes. Following API design best practices including resource-based URLs, proper HTTP methods, versioning, pagination, filtering, consistent naming, thorough documentation, graceful error handling, and security measures creates professional APIs consumed reliably by various clients. Understanding Django REST Framework fundamentals enables building sophisticated APIs powering mobile applications, single-page applications, microservices architectures, and third-party integrations throughout modern web development as DRF continues evolving with Django 6.0.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


