Lecture : 13 minutes · Niveau : intermédiaire · Mise à jour : avril 2026
Django REST Framework (DRF) est le standard pour bâtir des API REST en Django. Mature, riche, productif, il couvre la quasi-totalité des besoins API modernes : sérialisation, validation, authentification, permissions, pagination, throttling, filtres, documentation automatique. Ce guide rassemble les patterns vraiment utiles pour livrer des API robustes en production.
DRF a été créé en 2014 et a évolué constamment depuis. Sa stabilité d’API est l’une des raisons de son succès : un projet écrit avec DRF il y a cinq ans fonctionne encore aujourd’hui avec des ajustements minimes. Pour une PME qui veut investir dans une API durable sans se préoccuper de migrations majeures tous les six mois, c’est un atout important.
L’approche DRF se distingue par sa philosophie déclarative : on décrit la forme des données via des serializers, le comportement via des viewsets, les règles d’accès via des permissions. Le framework assemble le tout. Cette séparation des préoccupations rend le code testable, lisible, et facilement modifiable. Pour des équipes qui livrent des APIs internes ou publiques régulièrement, DRF accélère drastiquement la vélocité.
L’apprentissage initial demande de comprendre quelques concepts spécifiques (serializers, viewsets, routers, permissions) qui peuvent dérouter venant d’autres frameworks comme Express ou Flask. Une fois ces concepts intégrés, la productivité monte rapidement.
Voir aussi → Django pour PME : guide backend Python.
Sommaire
- Setup DRF dans un projet Django
- Serializers : sérialiser et valider
- Views et ViewSets
- Routers et URLs
- Filtres, recherche, ordering
- Pagination
- Permissions et authentification
- JWT avec SimpleJWT
- Throttling et rate limiting
- Documentation automatique avec drf-spectacular
- Tests d’API
- FAQ
1. Setup DRF dans un projet Django
uv add djangorestframework
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
]
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_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour',
'user': '1000/hour',
},
}
Cette configuration centralisée définit le comportement par défaut de tous les endpoints DRF. Override possible par viewset si besoin spécifique.
2. Serializers : sérialiser et valider
Les serializers transforment les modèles Django en JSON et vice-versa, avec validation.
# clients/serializers.py
from rest_framework import serializers
from .models import Client, Order
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'reference', 'total', 'status', 'created_at']
class ClientSerializer(serializers.ModelSerializer):
orders_count = serializers.IntegerField(read_only=True)
full_address = serializers.SerializerMethodField()
class Meta:
model = Client
fields = [
'id', 'nom', 'email', 'telephone', 'secteur', 'actif',
'orders_count', 'full_address', 'created_at',
]
read_only_fields = ['id', 'created_at']
extra_kwargs = {
'email': {'required': True},
}
def get_full_address(self, obj):
if not obj.address:
return None
return f"{obj.address.rue}, {obj.address.ville}"
def validate_email(self, value):
if 'example.test' in value:
raise serializers.ValidationError("Email de test non accepté.")
return value
def validate(self, data):
# Validation cross-field si besoin
if data.get('actif') and not data.get('email'):
raise serializers.ValidationError("Email obligatoire pour client actif.")
return data
Serializers imbriqués
Pour exposer une relation imbriquée :
class ClientWithOrdersSerializer(serializers.ModelSerializer):
orders = OrderSerializer(many=True, read_only=True)
class Meta:
model = Client
fields = ['id', 'nom', 'orders']
Pour la création avec relations imbriquées : surcharger create() et update(). C’est verbeux mais permet un contrôle précis.
Serializers différents selon action
class ClientListSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'nom', 'email'] # léger pour la liste
class ClientDetailSerializer(serializers.ModelSerializer):
orders = OrderSerializer(many=True, read_only=True)
class Meta:
model = Client
fields = '__all__'
# Dans le ViewSet
def get_serializer_class(self):
if self.action == 'list':
return ClientListSerializer
return ClientDetailSerializer
Optimisation : la liste retourne moins de champs, plus rapide.
3. Views et ViewSets
DRF propose plusieurs niveaux d’abstraction.
@api_view (function-based, simple)
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(['GET', 'POST'])
def client_list(request):
if request.method == 'GET':
clients = Client.objects.filter(actif=True)
serializer = ClientSerializer(clients, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = ClientSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
Class-based generic views
from rest_framework import generics
class ClientList(generics.ListCreateAPIView):
queryset = Client.objects.filter(actif=True)
serializer_class = ClientSerializer
class ClientDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Client.objects.all()
serializer_class = ClientSerializer
ViewSets (recommandé)
from rest_framework import viewsets
class ClientViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ClientSerializer
def get_queryset(self):
qs = super().get_queryset()
if self.action == 'list':
qs = qs.filter(actif=True).select_related('country').prefetch_related('orders')
return qs
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
@action(detail=True, methods=['post'])
def archiver(self, request, pk=None):
client = self.get_object()
client.actif = False
client.save()
return Response({'status': 'archivé'})
@action(detail=True) ajoute des actions custom à un ViewSet (par exemple POST /clients/1/archiver/).
ViewSets + Routers = boilerplate minimal pour des CRUD complets.
4. Routers et URLs
# api/urls.py
from rest_framework.routers import DefaultRouter
from clients.views import ClientViewSet
from orders.views import OrderViewSet
router = DefaultRouter()
router.register(r'clients', ClientViewSet)
router.register(r'orders', OrderViewSet)
urlpatterns = router.urls
# monsite/urls.py
urlpatterns = [
path('api/', include('api.urls')),
]
Le router génère automatiquement les URLs :
– GET /api/clients/ (liste)
– POST /api/clients/ (création)
– GET /api/clients/1/ (détail)
– PUT /api/clients/1/ (update complet)
– PATCH /api/clients/1/ (update partiel)
– DELETE /api/clients/1/ (suppression)
– POST /api/clients/1/archiver/ (action custom)
5. Filtres, recherche, ordering
uv add django-filter
class ClientViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ClientSerializer
filterset_fields = ['secteur', 'actif']
search_fields = ['nom', 'email']
ordering_fields = ['nom', 'created_at']
ordering = ['-created_at']
Usage :
– GET /api/clients/?secteur=agro&actif=true
– GET /api/clients/?search=acme
– GET /api/clients/?ordering=-created_at
FilterSet custom
import django_filters
class ClientFilter(django_filters.FilterSet):
created_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
created_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
nom = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = Client
fields = ['secteur', 'actif', 'nom', 'created_after', 'created_before']
class ClientViewSet(viewsets.ModelViewSet):
filterset_class = ClientFilter
Permet des filtres plus sophistiqués (intervalles de dates, lookup spécifiques).
6. Pagination
Trois styles fournis par DRF.
PageNumberPagination (défaut)
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
}
Réponse :
{
"count": 1234,
"next": "https://api.example.com/clients/?page=3",
"previous": "https://api.example.com/clients/?page=1",
"results": [...]
}
LimitOffsetPagination
?limit=20&offset=40 — flexible mais inefficace sur gros datasets.
CursorPagination
class CursorClientPagination(CursorPagination):
page_size = 20
ordering = '-created_at'
Performant sur millions de lignes (utilise un cursor sur un champ indexé). Pas de page jump, mais next/previous fluides.
Pour des feeds infinis (timeline, listes longues) : CursorPagination. Pour de la pagination classique avec choix de page : PageNumber.
7. Permissions et authentification
Permissions intégrées
from rest_framework.permissions import (
AllowAny, IsAuthenticated, IsAdminUser,
IsAuthenticatedOrReadOnly, DjangoModelPermissions,
)
class ClientViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, DjangoModelPermissions]
Custom permission
class IsOwnerOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.created_by == request.user
class ClientViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
Permissions par action
def get_permissions(self):
if self.action in ['list', 'retrieve']:
return [IsAuthenticated()]
return [IsAdminUser()]
8. JWT avec SimpleJWT
uv add djangorestframework-simplejwt
# settings.py
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,
}
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view()),
path('api/token/refresh/', TokenRefreshView.as_view()),
]
Workflow client :
1. POST /api/token/ avec username/password → reçoit access + refresh
2. Utilise access dans header Authorization: Bearer ...
3. Quand access expire (401) → POST /api/token/refresh/ avec refresh → nouvel access
4. Logout → blacklist le refresh
Pour SPA et apps mobiles : standard 2026.
9. Throttling et rate limiting
Protection contre brute-force et abus.
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour',
'user': '1000/hour',
'login': '5/minute',
},
}
Throttle ciblé
class LoginThrottle(AnonRateThrottle):
scope = 'login'
class LoginView(APIView):
throttle_classes = [LoginThrottle]
# ...
Indispensable sur endpoints sensibles : login, password reset, OTP. Sans throttling, un attaquant peut tenter des milliers de combinaisons login/password à la seconde sur une API. Quelques lignes de configuration neutralisent cette classe d’attaque.
10. Documentation automatique avec drf-spectacular
uv add drf-spectacular
INSTALLED_APPS += ['drf_spectacular']
REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'drf_spectacular.openapi.AutoSchema'
SPECTACULAR_SETTINGS = {
'TITLE': 'Mon API',
'DESCRIPTION': 'API de la PME Acme',
'VERSION': '1.0.0',
}
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns += [
path('api/schema/', SpectacularAPIView.as_view()),
path('api/docs/', SpectacularSwaggerView.as_view()),
]
Génère un schéma OpenAPI 3 + UI Swagger accessible sur /api/docs/. Documentation auto-générée depuis serializers, views, types.
Pour des cas complexes : @extend_schema décorateur sur les vues pour enrichir la doc avec exemples, descriptions custom, types de réponse spécifiques par status code. La documentation devient un livrable maintenu en parallèle du code, plutôt qu’un document Word qui se désynchronise rapidement.
11. Tests d’API
# tests/test_clients_api.py
from rest_framework.test import APITestCase
from rest_framework import status
from django.urls import reverse
from clients.models import Client
from users.models import User
class ClientAPITest(APITestCase):
def setUp(self):
self.user = User.objects.create_user(username='test', password='secret')
self.client.force_authenticate(user=self.user)
def test_list_clients(self):
Client.objects.create(nom='Acme', email='a@test.com')
url = reverse('client-list')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 1)
def test_create_client(self):
url = reverse('client-list')
data = {'nom': 'Beta', 'email': 'b@test.com'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_invalid_email(self):
url = reverse('client-list')
data = {'nom': 'Test', 'email': 'pas-un-email'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
APITestCase fournit un client REST de test. Très lisible.
Avec pytest + pytest-django : encore plus expressif via fixtures.
Stratégie de tests d’API
Une suite de tests API mature couvre plusieurs niveaux. Les tests unitaires sur les serializers vérifient les transformations et validations isolément. Les tests d’intégration sur les viewsets vérifient le comportement HTTP complet (codes statut, structure de réponse, side effects). Les tests de permissions vérifient que chaque endpoint refuse correctement les utilisateurs non autorisés. Pour des APIs critiques métier, des tests de bout en bout via httpx ou requests contre une instance déployée valident le comportement réel y compris middleware et configuration.
Pour ne pas alourdir la suite, cibler 60-70% de couverture sur la logique métier et les cas critiques (parcours d’achat, authentification, validations sensibles), plutôt que 100% sur tout. Les tests qui ne servent qu’à atteindre un nombre n’apportent pas de valeur ; ceux qui attrapent les régressions réelles oui.
12. FAQ
Function-based ou class-based views ?
Class-based (ViewSets) pour les CRUD standards : moins de boilerplate, conventions DRF. Function-based pour les endpoints très spécifiques où la logique custom domine. La plupart des projets utilisent les deux selon le cas.
ModelSerializer ou Serializer simple ?
ModelSerializer dès qu’on travaille avec un modèle Django : configuration automatique des champs, validations héritées du modèle, méthodes create/update fournies. Serializer simple pour des inputs/outputs qui ne correspondent pas à un modèle.
Comment versionner une API DRF ?
Plusieurs stratégies : URL (/api/v1/, /api/v2/), header (Accept-Version: 2), query param. URL est le plus simple à debugger et le plus utilisé. DRF supporte les trois via DEFAULT_VERSIONING_CLASS.
JWT en cookie ou en header ?
Header (Authorization: Bearer) pour des SPA et apps mobiles. Cookie httpOnly+Secure pour des sites où le frontend est sur le même domaine et où on veut éviter le risque XSS sur le storage. Les deux sont valides selon contexte.
Comment gérer les fichiers uploadés ?
DRF gère nativement multipart. Champ FileField/ImageField dans le serializer. Pour gros fichiers : streaming, ou upload direct vers S3 avec presigned URLs. Pour traitement async : déclencher un job Celery après upload.
Faut-il toujours utiliser DRF ?
Pour des API JSON sérieuses : oui. Pour des endpoints très simples (1-2 routes) : Django seul avec JsonResponse peut suffire. Pour des API ultra-performantes async-first : FastAPI à part. DRF reste la valeur sûre pour la majorité des projets Django.
Comment cacher des endpoints ?
from django.views.decorators.cache import cache_page
from rest_framework.decorators import action
@method_decorator(cache_page(60 * 5), name='list')
class ClientViewSet(viewsets.ModelViewSet):
...
Ou cache.set programmatique avec invalidation custom. Attention au cache d’API par utilisateur : nécessite des clés cache spécifiques par user/permission.
Articles liés (cluster Django)
- 👉 Django pour PME : guide backend Python (pillar)
- 👉 Django ORM et migrations
- 👉 Django déploiement production
Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.