Django REST Framework: The Complete Guide for 2026
Django REST Framework (DRF) is the industry-standard toolkit for building Web APIs with Django. It provides serializers for data conversion and validation, class-based views for handling HTTP methods, automatic URL routing, built-in authentication and permissions, and everything else you need to ship production APIs without reinventing the wheel.
This guide covers DRF from installation to production: serializers, ModelSerializer, views (APIView through ModelViewSet), routers, authentication (Token, JWT, Session), permissions, filtering, pagination, throttling, testing, and the best practices that separate hobby projects from production systems.
Table of Contents
1. What Is Django REST Framework
DRF sits on top of Django and adds everything you need to build RESTful APIs. While Django handles models, migrations, the ORM, and admin, DRF provides the API layer: serialization (converting model instances to JSON and back), request parsing, response rendering, authentication, permissions, and browsable API documentation out of the box.
DRF is used by companies like Mozilla, Red Hat, Eventbrite, and thousands of startups. It has over 28,000 GitHub stars and a mature ecosystem of third-party packages for JWT authentication, API documentation (drf-spectacular), filtering (django-filter), and more.
You should use DRF when you need to build a REST API for a single-page application frontend, mobile app, or third-party integrations on top of an existing Django project. If you are starting a new API-only project and do not need Django's template system or admin, consider FastAPI as a lighter alternative.
2. Installation and Setup
Install DRF into your Django project:
pip install djangorestframework
Add it to INSTALLED_APPS in your Django settings:
# settings.py
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", # your app
]
# Global DRF configuration
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
],
}
Include the DRF login URLs for the browsable API (optional but helpful during development):
# urls.py
from django.urls import path, include
urlpatterns = [
path("api/", include("myapp.urls")),
path("api-auth/", include("rest_framework.urls")), # browsable API login
]
We will use this model throughout the guide:
# myapp/models.py
from django.db import models
from django.conf import settings
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
body = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
status = models.CharField(
max_length=20,
choices=[("draft", "Draft"), ("published", "Published")],
default="draft",
)
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
3. Serializers
Serializers convert Django model instances and querysets into Python dictionaries (which DRF then renders as JSON) and handle the reverse: parsing incoming JSON, validating it, and producing validated data you can save. Think of them as Django forms for APIs.
A basic Serializer defines each field explicitly:
# myapp/serializers.py
from rest_framework import serializers
class ArticleSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=200)
slug = serializers.SlugField()
body = serializers.CharField()
author = serializers.PrimaryKeyRelatedField(read_only=True)
status = serializers.ChoiceField(choices=["draft", "published"])
created_at = serializers.DateTimeField(read_only=True)
def create(self, validated_data):
return Article.objects.create(**validated_data)
def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Use the serializer to serialize (output) and deserialize (input) data:
# Serialization: model instance -> dict -> JSON
article = Article.objects.first()
serializer = ArticleSerializer(article)
serializer.data # {"id": 1, "title": "...", "slug": "...", ...}
# Serializing multiple objects
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
serializer.data # [{"id": 1, ...}, {"id": 2, ...}]
# Deserialization: JSON -> validated dict -> model instance
data = {"title": "New Article", "slug": "new-article", "body": "Content", "status": "draft"}
serializer = ArticleSerializer(data=data)
serializer.is_valid(raise_exception=True) # raises ValidationError on failure
article = serializer.save(author=request.user)
Field-Level and Object-Level Validation
class ArticleSerializer(serializers.Serializer):
title = serializers.CharField(max_length=200)
slug = serializers.SlugField()
status = serializers.ChoiceField(choices=["draft", "published"])
# Field-level validation: validate_<field_name>
def validate_title(self, value):
if value.lower() == "untitled":
raise serializers.ValidationError("Title cannot be 'Untitled'.")
return value
# Object-level validation: access all fields
def validate(self, data):
if data.get("status") == "published" and len(data.get("body", "")) < 100:
raise serializers.ValidationError(
"Published articles must have at least 100 characters."
)
return data
4. ModelSerializer
ModelSerializer auto-generates serializer fields from a Django model, including appropriate validators. This is what you will use 90% of the time:
from rest_framework import serializers
from myapp.models import Article
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.get_full_name", read_only=True)
class Meta:
model = Article
fields = ["id", "title", "slug", "body", "author", "author_name",
"status", "created_at", "updated_at"]
read_only_fields = ["id", "author", "created_at", "updated_at"]
Key Meta options: fields explicitly lists which fields to include (never use "__all__" in production since it exposes every field). read_only_fields marks fields that clients cannot set. extra_kwargs lets you customize individual field behavior:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ["id", "title", "slug", "body", "status", "author"]
read_only_fields = ["id", "author"]
extra_kwargs = {
"body": {"min_length": 50, "help_text": "Article content (min 50 chars)"},
"slug": {"validators": []}, # override default unique validator
}
Nested Serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email"]
class ArticleDetailSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True) # nested representation
class Meta:
model = Article
fields = ["id", "title", "slug", "body", "author", "status", "created_at"]
# Output:
# {
# "id": 1,
# "title": "My Article",
# "author": {"id": 5, "username": "alice", "email": "alice@example.com"},
# ...
# }
5. Views: APIView
APIView is the base class for DRF views. You define methods for each HTTP verb (get, post, put, delete). DRF handles content negotiation, request parsing, authentication, permissions, and exception handling:
# myapp/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from myapp.models import Article
from myapp.serializers import ArticleSerializer
class ArticleListView(APIView):
def get(self, request):
articles = Article.objects.filter(status="published")
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ArticleSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class ArticleDetailView(APIView):
def get_object(self, slug):
try:
return Article.objects.get(slug=slug)
except Article.DoesNotExist:
raise NotFound("Article not found.")
def get(self, request, slug):
article = self.get_object(slug)
serializer = ArticleSerializer(article)
return Response(serializer.data)
def put(self, request, slug):
article = self.get_object(slug)
serializer = ArticleSerializer(article, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
def delete(self, request, slug):
article = self.get_object(slug)
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Wire up the URLs:
# myapp/urls.py
from django.urls import path
from myapp.views import ArticleListView, ArticleDetailView
urlpatterns = [
path("articles/", ArticleListView.as_view(), name="article-list"),
path("articles/<slug:slug>/", ArticleDetailView.as_view(), name="article-detail"),
]
6. Generic Views and Mixins
DRF provides generic views that eliminate boilerplate for standard CRUD operations. They combine GenericAPIView (which adds queryset and serializer_class) with mixin classes:
from rest_framework import generics
from myapp.models import Article
from myapp.serializers import ArticleSerializer
# List + Create in one view
class ArticleListCreateView(generics.ListCreateAPIView):
queryset = Article.objects.filter(status="published")
serializer_class = ArticleSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# Retrieve + Update + Destroy in one view
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = "slug"
Available generic views: ListAPIView, CreateAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView, ListCreateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, and RetrieveUpdateDestroyAPIView. Each combines the appropriate mixins automatically.
Override perform_create, perform_update, or perform_destroy to add custom logic (like setting the author or sending notifications) without rewriting the entire view.
7. ViewSets and Routers
ViewSets combine all related views (list, create, retrieve, update, destroy) into a single class. Routers then auto-generate the URL patterns:
# myapp/views.py
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from myapp.models import Article
from myapp.serializers import ArticleSerializer, ArticleDetailSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = "slug"
def get_serializer_class(self):
if self.action == "retrieve":
return ArticleDetailSerializer
return ArticleSerializer
def get_queryset(self):
qs = super().get_queryset()
if self.action == "list":
return qs.filter(status="published")
return qs
def perform_create(self, serializer):
serializer.save(author=self.request.user)
# Custom action: POST /api/articles/{slug}/publish/
@action(detail=True, methods=["post"])
def publish(self, request, slug=None):
article = self.get_object()
article.status = "published"
article.save()
return Response({"status": "published"})
Register the ViewSet with a router:
# myapp/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from myapp.views import ArticleViewSet
router = DefaultRouter()
router.register("articles", ArticleViewSet, basename="article")
urlpatterns = [
path("", include(router.urls)),
]
# Generated URLs:
# GET /articles/ -> list
# POST /articles/ -> create
# GET /articles/{slug}/ -> retrieve
# PUT /articles/{slug}/ -> update
# PATCH /articles/{slug}/ -> partial_update
# DELETE /articles/{slug}/ -> destroy
# POST /articles/{slug}/publish/ -> publish (custom action)
Use ModelViewSet for full CRUD. Use ReadOnlyModelViewSet when clients should only read data. Use viewsets.ViewSet as a base for fully custom logic without model binding.
8. Authentication
DRF supports multiple authentication schemes. Configure them globally or per-view.
Token Authentication
# settings.py
INSTALLED_APPS = [..., "rest_framework.authtoken"]
# Run: python manage.py migrate
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
],
}
# Create tokens for users
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=user)
print(token.key) # "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
# Client sends: Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
Add a login endpoint that returns a token:
# urls.py
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path("api/token/", obtain_auth_token), # POST username + password, returns token
]
JWT Authentication (SimpleJWT)
JWT tokens are stateless and more suitable for distributed systems:
# pip install djangorestframework-simplejwt
# settings.py
from datetime import timedelta
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True,
"SIGNING_KEY": SECRET_KEY,
"AUTH_HEADER_TYPES": ("Bearer",),
}
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path("api/token/", TokenObtainPairView.as_view()),
path("api/token/refresh/", TokenRefreshView.as_view()),
]
# Client usage:
# POST /api/token/ {"username": "alice", "password": "secret"}
# -> {"access": "eyJ...", "refresh": "eyJ..."}
#
# GET /api/articles/ with header: Authorization: Bearer eyJ...
#
# POST /api/token/refresh/ {"refresh": "eyJ..."}
# -> {"access": "new-eyJ..."}
Session Authentication
Session authentication uses Django's built-in session framework. It is enabled by default and works well for browser-based clients where your API and frontend share the same domain:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
}
# Session auth includes CSRF protection for non-safe methods (POST, PUT, DELETE)
9. Permissions
Permissions control who can access your API endpoints. DRF checks permissions after authentication:
# Built-in permission classes
from rest_framework.permissions import (
AllowAny, # no restrictions
IsAuthenticated, # logged-in users only
IsAdminUser, # staff users only (user.is_staff)
IsAuthenticatedOrReadOnly, # anyone can GET, auth required for POST/PUT/DELETE
)
class ArticleViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly]
Custom Permissions
# myapp/permissions.py
from rest_framework.permissions import BasePermission
class IsAuthorOrReadOnly(BasePermission):
"""Allow authors to edit their own articles. Everyone else gets read-only."""
def has_object_permission(self, request, view, obj):
# Safe methods (GET, HEAD, OPTIONS) are always allowed
if request.method in ("GET", "HEAD", "OPTIONS"):
return True
# Write permissions only for the article author
return obj.author == request.user
class IsEditorOrAdmin(BasePermission):
"""Only editors and admins can access this endpoint."""
def has_permission(self, request, view):
return request.user.groups.filter(
name__in=["editors", "admins"]
).exists()
# Usage in views
class ArticleViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
def get_permissions(self):
if self.action == "create":
return [IsAuthenticated()]
if self.action in ("update", "partial_update", "destroy"):
return [IsAuthenticated(), IsAuthorOrReadOnly()]
return [AllowAny()]
10. Filtering, Searching, and Ordering
Install django-filter for powerful filtering:
pip install django-filter
# settings.py
INSTALLED_APPS = [..., "django_filters"]
REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
],
}
Add filtering to your views:
from rest_framework import viewsets
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
# Exact-match filtering: ?status=published&author=5
filterset_fields = ["status", "author"]
# Text search: ?search=django rest
search_fields = ["title", "body", "author__username"]
# Ordering: ?ordering=-created_at or ?ordering=title
ordering_fields = ["created_at", "updated_at", "title"]
ordering = ["-created_at"] # default ordering
Custom FilterSet
import django_filters
from myapp.models import Article
class ArticleFilterSet(django_filters.FilterSet):
title = django_filters.CharFilter(lookup_expr="icontains")
created_after = django_filters.DateFilter(field_name="created_at", lookup_expr="gte")
created_before = django_filters.DateFilter(field_name="created_at", lookup_expr="lte")
author_name = django_filters.CharFilter(
field_name="author__username", lookup_expr="icontains"
)
class Meta:
model = Article
fields = ["status", "title", "created_after", "created_before", "author_name"]
class ArticleViewSet(viewsets.ModelViewSet):
filterset_class = ArticleFilterSet
# ?title=django&status=published&created_after=2026-01-01
11. Pagination
DRF provides three built-in pagination styles:
# settings.py -- global default
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}
# Custom pagination classes
from rest_framework.pagination import (
PageNumberPagination,
LimitOffsetPagination,
CursorPagination,
)
# Page number: ?page=2
class StandardPagination(PageNumberPagination):
page_size = 20
page_size_query_param = "page_size" # ?page_size=50
max_page_size = 100
# Limit/Offset: ?limit=20&offset=40
class LimitOffsetPagination(LimitOffsetPagination):
default_limit = 20
max_limit = 100
# Cursor (best for real-time feeds, no count query):
class ArticleCursorPagination(CursorPagination):
page_size = 20
ordering = "-created_at"
cursor_query_param = "cursor"
# Apply per-view
class ArticleViewSet(viewsets.ModelViewSet):
pagination_class = StandardPagination
Page number pagination returns count, next, previous, and results. Cursor pagination is the most performant for large datasets because it does not require a COUNT query and prevents clients from jumping to arbitrary pages (which can cause inconsistent results with real-time data).
12. Throttling
Throttling limits how many requests a client can make in a given time period. It protects your API from abuse and ensures fair usage:
# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/hour", # unauthenticated users
"user": "1000/hour", # authenticated users
},
}
Custom Throttle Scopes
from rest_framework.throttling import ScopedRateThrottle
# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.ScopedRateThrottle"],
"DEFAULT_THROTTLE_RATES": {
"uploads": "10/hour",
"search": "60/minute",
"contact": "5/day",
},
}
# views.py
class FileUploadView(APIView):
throttle_scope = "uploads" # max 10 uploads per hour
class SearchView(APIView):
throttle_scope = "search" # max 60 searches per minute
13. Testing APIs
DRF provides APIClient and APITestCase built on Django's test framework. Use them to test your endpoints with proper authentication and content negotiation:
# myapp/tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.test import APIClient
from rest_framework import status
from myapp.models import Article
class ArticleAPITests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(
username="testuser", password="testpass123"
)
self.article = Article.objects.create(
title="Test Article",
slug="test-article",
body="Test body content that is long enough.",
author=self.user,
status="published",
)
def test_list_articles(self):
response = self.client.get("/api/articles/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 1)
def test_create_article_authenticated(self):
self.client.force_authenticate(user=self.user)
data = {
"title": "New Article",
"slug": "new-article",
"body": "Content for the new article with enough length.",
"status": "draft",
}
response = self.client.post("/api/articles/", data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Article.objects.count(), 2)
self.assertEqual(response.data["author"], self.user.id)
def test_create_article_unauthenticated(self):
data = {"title": "Fail", "slug": "fail", "body": "No auth", "status": "draft"}
response = self.client.post("/api/articles/", data, format="json")
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_update_own_article(self):
self.client.force_authenticate(user=self.user)
response = self.client.patch(
f"/api/articles/{self.article.slug}/",
{"title": "Updated Title"},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.article.refresh_from_db()
self.assertEqual(self.article.title, "Updated Title")
def test_delete_other_users_article(self):
other_user = User.objects.create_user("other", password="pass123")
self.client.force_authenticate(user=other_user)
response = self.client.delete(f"/api/articles/{self.article.slug}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
For JWT-authenticated tests:
from rest_framework_simplejwt.tokens import RefreshToken
class JWTArticleTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user("alice", password="secret")
refresh = RefreshToken.for_user(self.user)
self.client.credentials(
HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}"
)
def test_authenticated_request(self):
response = self.client.get("/api/articles/")
self.assertEqual(response.status_code, 200)
Run tests with python manage.py test myapp or use pytest with pytest-django for more flexibility.
14. Best Practices
Never use fields = "__all__" in serializers. Always explicitly list fields. Using __all__ exposes every model field, including sensitive ones you add later. Explicit field lists are self-documenting and prevent accidental data leaks.
Use separate serializers for read and write. Your list endpoint might return nested author data (read), but your create endpoint only needs an author ID (write). Use get_serializer_class() on your ViewSet to return different serializers based on the action.
Version your API. Use URL versioning (/api/v1/articles/) or header versioning. DRF supports both. Plan for versioning from day one because adding it later is painful:
# settings.py
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"ALLOWED_VERSIONS": ["v1", "v2"],
"DEFAULT_VERSION": "v1",
}
# urls.py
urlpatterns = [
path("api/v1/", include("myapp.urls_v1")),
path("api/v2/", include("myapp.urls_v2")),
]
Select related objects in querysets. N+1 queries are the most common DRF performance issue. If your serializer accesses article.author.username, every article in a list triggers a separate database query for the author. Fix it with select_related and prefetch_related:
class ArticleViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Article.objects.select_related("author").prefetch_related("tags")
Handle errors consistently. DRF's default exception handler returns structured error responses. Customize it for a consistent API error format:
# myapp/exceptions.py
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
response.data = {
"error": True,
"status_code": response.status_code,
"detail": response.data,
}
return response
# settings.py
REST_FRAMEWORK = {
"EXCEPTION_HANDLER": "myapp.exceptions.custom_exception_handler",
}
Generate API documentation. Use drf-spectacular for OpenAPI 3.0 schema generation. It produces Swagger UI and ReDoc documentation automatically:
# pip install drf-spectacular
# settings.py
INSTALLED_APPS = [..., "drf_spectacular"]
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
# urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema")),
]
Use environment variables for secrets. Never hardcode SECRET_KEY, database credentials, or API keys. Use django-environ or python-decouple and keep secrets in environment variables or a .env file excluded from version control.
Frequently Asked Questions
What is Django REST Framework and why should I use it?
Django REST Framework (DRF) is a powerful toolkit for building Web APIs on top of Django. It provides serializers for converting complex data types to JSON, class-based views and viewsets for handling HTTP methods, automatic URL routing, built-in authentication (Token, Session, JWT via third-party), permissions, filtering, pagination, and throttling. DRF saves months of development compared to building API infrastructure from scratch and is the standard choice for Django API projects.
What is the difference between APIView, GenericAPIView, and ViewSet?
APIView is the base class that maps HTTP methods (get, post, put, delete) to handler methods, giving you full control. GenericAPIView adds queryset and serializer_class plus mixin classes for common patterns. ViewSet combines multiple related views into a single class with actions like list, create, retrieve, update, and destroy, and works with routers for automatic URL configuration. Use APIView for custom logic, GenericAPIView with mixins for standard CRUD with some customization, and ModelViewSet for rapid full-CRUD endpoints.
How do I implement JWT authentication in Django REST Framework?
Install djangorestframework-simplejwt, add JWTAuthentication to DEFAULT_AUTHENTICATION_CLASSES in your DRF settings, and include the token obtain and refresh URL patterns. Clients POST credentials to /api/token/ to receive access and refresh tokens, then include Authorization: Bearer <access_token> in subsequent requests. Configure token lifetimes via SIMPLE_JWT in your settings. See the full Authentication section for working code examples.
How do serializers work in Django REST Framework?
Serializers convert complex data types like Django model instances into Python dictionaries that DRF renders as JSON. They also handle deserialization: parsing incoming JSON, validating it, and converting it back to model instances. ModelSerializer auto-generates fields from a Django model. You define which fields to include, add custom validation methods (validate_<field> or validate), and can nest serializers for related objects. Call serializer.is_valid() before accessing validated_data or save(). See the full Serializers section for details.
How do I add filtering, searching, and ordering to DRF endpoints?
Install django-filter and add DjangoFilterBackend, SearchFilter, and OrderingFilter to your filter backends. Set filterset_fields for exact-match filtering (?status=published), search_fields for text search (?search=django), and ordering_fields for sorting (?ordering=-created_at). For complex filtering, create a custom FilterSet class with range lookups and related field filtering. See the full Filtering section for code examples.