Por qué documentar con OpenAPI
Una API REST sin documentación ES una API inutilizable. Los integradores (front-end, móvil, partners) necesitan saber:
- Qué endpoints existen.
- Qué parámetros acepta cada uno.
- Qué forma tiene la respuesta.
- Qué códigos de error puede devolver.
- Cómo autenticarse.
Mantener la documentación a mano (en Confluence, Notion o un README) siempre se desactualiza. La única forma sostenible es generarla a partir del código mediante un schema OpenAPI (antes llamado Swagger).
DRF viene con un sistema de schema incorporado, pero limitado y deprecado. El estándar actual es drf-spectacular: genera OpenAPI 3.1 completo, con Swagger UI interactivo, y soporta todas las particularidades de DRF (polimorfismo, paginación, filtros, autenticación).
Instalación y configuración mínima
pip install drf-spectacular
En settings.py:
INSTALLED_APPS = [
...
'rest_framework',
'drf_spectacular',
]
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'Mi API de Pedidos',
'DESCRIPTION': 'API REST para gestionar pedidos, clientes y productos.',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'SWAGGER_UI_DIST': 'SIDECAR',
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
'REDOC_DIST': 'SIDECAR',
}
En urls.py del proyecto:
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularSwaggerView,
SpectacularRedocView,
)
urlpatterns = [
...
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]
Con solo eso, visitando /api/docs/ ya tienes Swagger UI funcional con todos tus ViewSets documentados automáticamente. SpectacularAPIView devuelve el JSON OpenAPI crudo, útil para clientes como openapi-generator que generan SDKs en otros lenguajes.
Schema automático a partir de serializers
drf-spectacular analiza tus ModelSerializer y genera los esquemas de request/response sin esfuerzo:
# serializers.py
class ProductoSerializer(serializers.ModelSerializer):
class Meta:
model = Producto
fields = ['id', 'nombre', 'precio', 'stock', 'categoria']
read_only_fields = ['id']
# views.py
class ProductoViewSet(viewsets.ModelViewSet):
queryset = Producto.objects.all()
serializer_class = ProductoSerializer
En Swagger UI aparecen automáticamente:
GET /api/productos/con respuesta[ProductoSerializer](paginated).POST /api/productos/con bodyProductoSerializery respuesta 201.GET /api/productos/{id}/con respuestaProductoSerializero 404.PUT /api/productos/{id}/yPATCH /api/productos/{id}/.DELETE /api/productos/{id}/con respuesta 204.
Personalizar con @extend_schema
El decorador @extend_schema permite sobrescribir cualquier aspecto del schema generado:
from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter
class ProductoViewSet(viewsets.ModelViewSet):
queryset = Producto.objects.all()
serializer_class = ProductoSerializer
@extend_schema(
summary="Listar productos activos",
description="Devuelve los productos con stock mayor que 0, "
"filtrables por categoría y ordenables por precio.",
tags=['Productos'],
parameters=[
OpenApiParameter(
name='categoria_id',
type=int,
location=OpenApiParameter.QUERY,
description='Filtrar por ID de categoría',
required=False,
),
OpenApiParameter(
name='ordering',
type=str,
location=OpenApiParameter.QUERY,
enum=['precio', '-precio', 'nombre', '-nombre'],
required=False,
),
],
responses={200: ProductoSerializer(many=True)},
examples=[
OpenApiExample(
'Ejemplo de producto',
value={'id': 1, 'nombre': 'Laptop', 'precio': 1200, 'stock': 5, 'categoria': 2},
response_only=True,
),
],
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
El resultado: Swagger UI muestra un endpoint con descripción clara, parámetros tipados con dropdown para ordering, y un ejemplo de respuesta.
Documentar acciones personalizadas (@action)
from rest_framework.decorators import action
from rest_framework.response import Response
class ProductoViewSet(viewsets.ModelViewSet):
...
@extend_schema(
summary="Actualizar stock",
request={'application/json': {
'type': 'object',
'properties': {'cantidad': {'type': 'integer', 'example': 10}},
'required': ['cantidad'],
}},
responses={200: ProductoSerializer, 400: {'description': 'Cantidad inválida'}},
)
@action(detail=True, methods=['post'])
def actualizar_stock(self, request, pk=None):
producto = self.get_object()
cantidad = request.data.get('cantidad')
if cantidad is None or not isinstance(cantidad, int):
return Response({'error': 'cantidad debe ser entero'}, status=400)
producto.stock += cantidad
producto.save()
return Response(ProductoSerializer(producto).data)
Ejemplos en el body del request
OpenApiExample permite mostrar distintos ejemplos en Swagger UI, útil para APIs con muchos casos:
@extend_schema(
examples=[
OpenApiExample(
'Producto físico',
value={'nombre': 'Laptop', 'precio': 1200, 'stock': 5, 'tipo': 'fisico'},
request_only=True,
),
OpenApiExample(
'Producto digital',
value={'nombre': 'Ebook', 'precio': 20, 'stock': -1, 'tipo': 'digital'},
request_only=True,
),
],
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
Polimorfismo: varios serializers de respuesta
Cuando un endpoint puede devolver distintos tipos según el caso, se usa PolymorphicProxySerializer:
from drf_spectacular.utils import PolymorphicProxySerializer
class ProductoFisicoSerializer(serializers.ModelSerializer): ...
class ProductoDigitalSerializer(serializers.ModelSerializer): ...
@extend_schema(
responses={200: PolymorphicProxySerializer(
component_name='ProductoUnion',
serializers=[ProductoFisicoSerializer, ProductoDigitalSerializer],
resource_type_field_name='tipo',
)},
)
def retrieve(self, request, *args, **kwargs):
...
Autenticación en el schema
drf-spectacular detecta las clases de autenticación de DRF y las documenta automáticamente. Si usas JWT con simplejwt:
SPECTACULAR_SETTINGS = {
...
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
'persistAuthorization': True,
'displayOperationId': True,
},
'SERVERS': [
{'url': 'https://api.miempresa.com', 'description': 'Producción'},
{'url': 'http://localhost:8000', 'description': 'Local'},
],
}
En Swagger UI aparece el botón Authorize donde el usuario puede pegar su token y probar endpoints autenticados.
Integración con drf-nested-routers y paginación
Las dependencias con plugins comunes de DRF se detectan automáticamente:
- Paginación (
PageNumberPagination,LimitOffsetPagination,CursorPagination): se documentan los parámetrospage,page_size,limit,offset,cursor. - Filtros (
DjangoFilterBackend,SearchFilter,OrderingFilter): se documentan los query params. - Permisos: se listan en
security.
Excluir vistas internas
Hay endpoints que no deben aparecer en la documentación pública (health checks, métricas):
@extend_schema(exclude=True)
class HealthCheckView(APIView):
def get(self, request):
return Response({'status': 'ok'})
Validar el schema en CI
En tu pipeline, puedes detectar cambios accidentales al schema:
# Generar el schema actual
python manage.py spectacular --color --file schema.yml
# Comprobar con el versionado
git diff --exit-code schema.yml
# Si hay diff, el schema cambió. Documentar el cambio en el changelog.
O mejor, usar spectacular-check en pre-commit para garantizar que siempre está al día.
Generar SDKs de cliente
El JSON OpenAPI producido por SpectacularAPIView es compatible con openapi-generator. Puedes regalar a tus integradores un SDK en Python, TypeScript, Go, Java, etc:
openapi-generator generate \
-i http://localhost:8000/api/schema/ \
-g typescript-axios \
-o ./clients/typescript
Esto genera un cliente TypeScript con typing completo, métodos por endpoint y manejo de errores. Los integradores B2B lo adorarán.
Patrón recomendado
- Instalar drf-spectacular y configurar los 3 endpoints al inicio del proyecto.
- Aceptar el schema automático como default. No sobrescribir nada salvo casos especiales.
- Solo usar
@extend_schemapara:- Añadir descripciones y tags útiles.
- Documentar acciones personalizadas con
@action. - Excluir vistas internas.
- Casos polimórficos o request/response no-triviales.
- Servir Swagger UI protegido (IsAdminUser) en producción, o accesible solo dentro de la VPN.
- En CI, comprobar que el schema no cambia inesperadamente.
Con esto, tu API DRF tiene documentación profesional auto-actualizada sin esfuerzo continuo.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Instalar drf-spectacular y configurar el schema OpenAPI 3.1 en settings.py. Registrar las rutas de Swagger UI y Redoc. Personalizar operaciones con @extend_schema (summary, description, tags, request, response, examples). Documentar ejemplos con OpenApiExample. Declarar parámetros de query con OpenApiParameter. Gestionar serializers polimórficos con PolymorphicProxySerializer.
Cursos que incluyen esta lección
Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje