djangorestframework
API toolkit layered on Django. Serializers validate input and convert models or plain Python objects, APIView adds content negotiation and policy hooks, generic views implement common CRUD flows, and viewsets plus routers generate related endpoints. Authentication, permissions, throttling, pagination, filtering, parsers, renderers, a browsable HTML API, and test clients use Django's ORM, settings, middleware, cache, and request lifecycle. It is the comprehensive Django-native choice, not a standalone ASGI framework or automatic API security boundary.
The default serious API toolkit for a Django application, with unusually complete policies and documentation. Do not choose it merely for a small JSON service, and never confuse generated CRUD, throttles, or serializer validation with a finished security design.
Use it if
- Your application already uses Django models, authentication, admin, settings, and migrations, and the API should share those rules
- You need conventional CRUD endpoints quickly but still want explicit serializer, permission, filter, and queryset hooks
- The browsable API will materially help internal users and backend developers inspect and exercise endpoints
- You have complex validation or representation rules that benefit from dedicated serializer classes rather than schema-only generation
- You are not already choosing Django: DRF brings Django's project structure, settings, ORM conventions, and request stack, which is excessive for a small independent service
- Your project must stay on Django 4.2, 5.0, or 5.1: DRF 3.18.0 explicitly dropped them and now requires Django 5.2 or newer plus Python 3.10+
- You are building an async-first service with long-lived or highly concurrent I/O handlers: the core APIView dispatch in 3.18 remains synchronous rather than an async endpoint model
- You expect built-in throttles to stop attacks or enforce exact quotas: the official docs say they are not a security measure, use non-atomic cache operations, and can allow extra requests under concurrency
- You want simple, automatic OpenAPI output to be the central contract: DRF's serializers, viewsets, custom actions, permissions, and dynamic querysets can require extra schema tooling and manual annotations
Setup reality
Version 3.18.0 requires Python 3.10+ and Django 5.2, 6.0, or 6.1, with the project recommending the latest patch of each series. Install djangorestframework, add rest_framework to INSTALLED_APPS, include API URLs, and run Django checks and migrations. That only makes the framework available; production safety depends on REST_FRAMEWORK defaults. Define authentication and permission classes globally or per view because an omitted permission policy can expose an endpoint. SessionAuthentication uses Django's session and CSRF model, which is good for same-site browser clients but surprises JavaScript callers that omit the CSRF token. Built-in TokenAuthentication needs rest_framework.authtoken in INSTALLED_APPS and a migration, and its simple database tokens may not meet rotation, expiry, or OAuth requirements. CORS is not supplied by DRF; configure it in Django middleware with a separate maintained package if browser origins differ. ModelViewSet can expose list, retrieve, create, update, partial_update, and destroy at once, so restrict http_method_names or use narrower generic views rather than assuming the router is read-only. Querysets must be scoped to request.user before object lookup, and custom actions need the same permission discipline. ModelSerializer infers fields and validators from models, but explicit fields are safer than __all__. Avoid N+1 responses by applying select_related and prefetch_related in get_queryset; serializers do not optimize ORM access. List endpoints need pagination and bounded filters before launch. If using DjangoFilterBackend, install and configure django-filter separately. Built-in throttles require a shared cache in multi-process deployments and are policy controls, not DDoS protection. DRF 3.18 changed many=True validation errors from list to dict form, so clients and tests that parse error bodies need migration work. Decide how exception responses, schema generation, versioning, and browsable API exposure work before declaring the API stable.
Patterns
Serialize and validate a modeldefine-model-serializer
from rest_framework import serializers
from .models import Project
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['id', 'name', 'owner', 'created_at']
read_only_fields = ['id', 'owner', 'created_at']List fields explicitly. read_only_fields prevents clients from assigning ownership or server-managed values.
Add field and object validationvalidate-input
class ProjectSerializer(serializers.ModelSerializer):
def validate_name(self, value):
value = value.strip()
if not value:
raise serializers.ValidationError('Name cannot be blank.')
return value
def validate(self, attrs):
if attrs.get('starts_at') and attrs.get('ends_at') and attrs['starts_at'] >= attrs['ends_at']:
raise serializers.ValidationError({'ends_at': 'Must be after starts_at.'})
return attrsOn partial updates, attrs may omit either field; read existing instance values when cross-field rules must still apply.
Expose owned records with a viewsetcreate-viewset
from rest_framework import permissions, viewsets
class ProjectViewSet(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Project.objects.filter(owner=self.request.user).select_related('owner')
def perform_create(self, serializer):
serializer.save(owner=self.request.user)Scope get_queryset before object lookup. Object permissions alone do not automatically filter list querysets.
Generate routes for a viewsetregister-router
from django.urls import include, path
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('projects', ProjectViewSet, basename='project')
urlpatterns = [path('api/', include(router.urls))]ModelViewSet generates write and delete routes too. Use ReadOnlyModelViewSet or narrower mixins when mutation is not intended.
Write an explicit APIViewbuild-api-view
from rest_framework import permissions, status, views
from rest_framework.response import Response
class HealthView(views.APIView):
permission_classes = [permissions.AllowAny]
def get(self, request):
return Response({'status': 'ok'}, status=status.HTTP_200_OK)APIView dispatch is synchronous in the current core. Keep slow external I/O out of this handler or move it to jobs.
Create a bounded list and create endpointuse-generic-view
from rest_framework import generics, permissions
class ProjectList(generics.ListCreateAPIView):
serializer_class = ProjectSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Project.objects.filter(owner=self.request.user).order_by('-created_at')
def perform_create(self, serializer):
serializer.save(owner=self.request.user)ListCreateAPIView exposes only GET and POST, which is safer than starting with every ModelViewSet action.
Add a permission-aware custom actionadd-viewset-action
from rest_framework.decorators import action
from rest_framework.response import Response
class ProjectViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAdminUser])
def archive(self, request, pk=None):
project = self.get_object()
project.archive()
return Response({'status': 'archived'})get_object applies queryset scoping and object permission checks; do not replace it with an unrestricted model lookup.
Add exact, search, and ordering filtersconfigure-filtering
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
class ProjectViewSet(viewsets.ModelViewSet):
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['status']
search_fields = ['name', 'description']
ordering_fields = ['created_at', 'name']
ordering = ['-created_at']Install django-filter for DjangoFilterBackend and allowlist ordering fields so clients cannot sort on sensitive or costly columns.
Set a capped page sizeconfigure-pagination
from rest_framework.pagination import PageNumberPagination
class ProjectPagination(PageNumberPagination):
page_size = 50
page_size_query_param = 'page_size'
max_page_size = 200
class ProjectViewSet(viewsets.ModelViewSet):
pagination_class = ProjectPaginationAlways cap client-selected page sizes; serialization and related queries can make an unbounded page expensive.
Set safe application defaultsconfigure-global-policies
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 50,
}SessionAuthentication requires CSRF protection for unsafe browser requests. Override AllowAny only on intentionally public views.
Throttle an expensive actionapply-business-throttle
from rest_framework.throttling import UserRateThrottle
class ExportThrottle(UserRateThrottle):
scope = 'exports'
class ExportView(views.APIView):
throttle_classes = [ExportThrottle]
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {'exports': '10/hour'},
}DRF throttles use non-atomic cache operations and are not DDoS or brute-force protection. Use shared cache for consistent policy across processes.
Test permissions and response shapetest-authenticated-endpoint
from rest_framework import status
from rest_framework.test import APITestCase
class ProjectApiTests(APITestCase):
def test_user_sees_only_owned_projects(self):
self.client.force_authenticate(user=self.user)
response = self.client.get('/api/projects/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual([item['id'] for item in response.data['results']], [self.project.id])force_authenticate bypasses the real authentication mechanism; keep separate integration tests for headers, tokens, sessions, and CSRF.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| django-ninja | PyPI | You want Django integration with type-hint-driven validation and OpenAPI closer to FastAPI's style |
| fastapi | PyPI | You are building an async-first standalone service and want OpenAPI generated directly from typed endpoints |
| flask-restx | PyPI | You already use Flask and want resource routing, request models, and Swagger documentation without adopting Django |