Django REST Framework
Django REST Framework (DRF) is the standard toolkit for building REST APIs with Django. It adds serializers that convert model instances to JSON and validate incoming data, class-based views and ViewSets that implement CRUD endpoints in a few lines, routers that generate URLs, and pluggable authentication, permissions, throttling, filtering, and pagination. Its browsable API makes endpoints explorable in a web browser.
DRF's layered design lets you start with a ModelViewSet that does everything, then override exactly the parts you need. The main pitfalls are performance (serializers that trigger N+1 queries) and security (permissions that don't cover object-level access).
TL;DR
- Serializers turn models into primitive data and validate input;
ModelSerializerderives fields from a model. - Views range from
APIView(full control) through generic views toModelViewSet(full CRUD), wired up with a router. - Authentication classes identify the user (session, token, JWT); permission classes decide access, including object-level checks.
- Built-in pagination, filtering (with
django-filter), ordering, search, and throttling. - Optimize
get_queryset()withselect_related/prefetch_relatedto avoid N+1 queries in nested serializers. - Generate OpenAPI docs with drf-spectacular.
Quick Example
This yields GET/POST /orders/, GET/PUT/PATCH/DELETE /orders/{id}/, and POST /orders/{id}/cancel/.
Core Concepts
Serializers
Serializers work in both directions:
- Output:
OrderSerializer(order).data→ a dict ready for JSON. - Input:
OrderSerializer(data=request.data), then.is_valid(raise_exception=True), then.save()callscreate()orupdate().
Validation layers: field types and options, validate_<field>() methods, object-level validate(), and validators like UniqueTogetherValidator. Use separate read and write serializers when the input and output shapes differ significantly. Nested writes aren't automatic; implement create()/update() explicitly for them.
Views: From APIView to ViewSets
Override hooks like get_queryset(), get_serializer_class(), perform_create(), and get_permissions() to customize behavior without rewriting handlers.
Authentication and Permissions
- Authentication classes:
SessionAuthentication(browser clients, CSRF-protected),TokenAuthentication, or JWT viadjangorestframework-simplejwt. For OAuth2 and OIDC, use django-oauth-toolkit or an external identity provider. See authentication. - Permission classes:
IsAuthenticated,IsAdminUser,DjangoModelPermissions, and custom classes withhas_permission(view-level) andhas_object_permission(object-level).
Object permissions run only when the view calls get_object(). List views must restrict data through get_queryset(), or users can list objects they shouldn't see.
Filtering, Pagination, and Throttling
Pagination options are PageNumberPagination, LimitOffsetPagination, and CursorPagination (stable and efficient for large, changing datasets). For rate limiting at scale, back throttles with a shared cache like Redis.
API Documentation
drf-spectacular generates an OpenAPI 3 schema from your views and serializers, served through Swagger UI or ReDoc. Refine it with @extend_schema decorators. The schema also powers client code generation. See API documentation.
Best Practices
Scope Querysets to the User
Make get_queryset() return only what the requesting user may access. It protects list, detail, update, and delete endpoints at once, and complements object-level permissions.
Optimize Serializer Queries
Nested serializers and source="relation.field" trigger lazy loads per object. Add matching select_related/prefetch_related in get_queryset(), and test query counts with assertNumQueries. See Django ORM.
Keep Business Logic Out of Serializers and Views
Serializers validate and transform; views handle HTTP. Put domain operations (cancelling orders, charging payments) in model methods or service functions that views call, which keeps them reusable from admin, tasks, and management commands.
Set Secure Defaults Globally
Default to IsAuthenticated, and opt out explicitly (AllowAny) for public endpoints. That's safer than remembering to protect each new view.
Common Mistakes
Exposing Every Model Field
Relying on Object Permissions for List Views
has_object_permission isn't called for list endpoints or custom queries. Without a filtered get_queryset(), GET /orders/ returns everyone's orders.
Heavy Logic in SerializerMethodField
A SerializerMethodField that runs a query per object is an N+1 in disguise. Precompute values with annotate() in the queryset and expose them as plain fields.
FAQ
Should I use DRF or Django Ninja?
DRF is mature, feature-rich, and widely known, with a large ecosystem of extensions. Django Ninja offers a FastAPI-like, type-hint-driven style with Pydantic schemas, automatic OpenAPI, and good async support. Both are solid choices. DRF is the conservative default, and Ninja is attractive for new, type-heavy projects.
How do I do JWT authentication with DRF?
Use djangorestframework-simplejwt: add its authentication class, and expose token obtain and refresh endpoints. Keep access tokens short-lived, and consider session authentication for first-party browser apps, where cookies with CSRF protection are simpler and safer than tokens in JavaScript.
How do I version a DRF API?
DRF supports URL path, namespace, header (Accept), and query-parameter versioning. request.version then lets views choose serializers. Many teams version only at the URL prefix (/api/v1/) and evolve additively within a version. See API versioning.
Does DRF support async views?
DRF's core is synchronous. It runs fine under ASGI, but views execute synchronously. For async-heavy APIs, consider Django's native async views, Django Ninja, or adapters like adrf. See async Django.
Related Topics
- Django — The framework overview
- Django ORM — Efficient querysets behind your endpoints
- Django Authentication — Users, sessions, and permissions
- REST API — REST design principles
- API Design — Designing clean, consistent APIs
- FastAPI — A Python alternative for API-first services