De cero a una API REST con Python · Caso: taller/cursos
Desarrollador Python/Django. Full Stack en Xprende Tech (Ecuador) e Infobibliotecas (España). Fundador de Python Piura.
Construimos una API REST paso a paso: del modelo al endpoint, con código que puedes llevarte.
Qué es una API REST y qué restricciones sigue.
Roy Fielding describe REST en su tesis doctoral: uno de los padres de la especificación HTTP.
No es una arquitectura de software: es un conjunto de restricciones para diseñar un estilo de arquitectura.
Una interfaz entre sistemas que usa HTTP para obtener datos u operar sobre ellos.
Débilmente acoplados: el cliente no conoce la implementación del servidor y viceversa. Pueden evolucionar por separado.
Cada recurso tiene una única dirección: su URI. /cursos/, /cursos/3/…
POST crear · GET leer · PUT editar · DELETE eliminar.
Cada petición es independiente: el servidor no necesita recordar sesiones. Todo lo necesario viaja en la petición.
Las respuestas deben poder marcarse como cacheables para evitar trabajo repetido.
El servidor puede implementarse en capas: balanceadores, cachés, proxies… el cliente no necesita saberlo.
| GET | Leer un recurso o una colección |
| POST | Crear un recurso |
| PUT | Reemplazar un recurso completo |
| PATCH | Actualizar parte de un recurso |
| DELETE | Eliminar un recurso |
| 200 | OK · la operación salió bien |
| 201 | Created · recurso creado |
| 400 | Bad Request · datos inválidos |
| 404 | Not Found · no existe |
| 500 | Server Error · fallo del servidor |
Frontend y backend en servidores distintos, con equipos y ciclos independientes. La interfaz es la API.
Puedes migrar servidores o cambiar la base de datos sin tocar al cliente, mientras se respete el contrato.
La misma API la consumen una web, una app móvil, otro servicio o una integración.
Recursos, métodos y códigos de estado: cualquiera puede integrarse sin leer tu código.
El framework web y su toolkit para APIs.
ORM, autenticación, admin, formularios, plantillas y migraciones: listo para producción.
Un proyecto se compone de aplicaciones con una responsabilidad cada una: cursos, usuarios…
Clases Python que se reflejan en tablas de la base de datos, sin escribir SQL a mano.
Una app de Django para construir APIs REST reutilizando piezas, con una API navegable desde el navegador.
Convierte modelos (y otros orígenes) a JSON y valida lo que entra: como los Forms, pero para APIs.
Vistas genéricas y ViewSets que implementan un CRUD completo con muy poco código.
Integración con sesiones, OAuth1a/2, tokens y JWT; permisos declarados por vista.
Documentación excelente y uso intensivo en producción (Mozilla, Red Hat, Heroku…).
| Vista Django | Django REST Framework | |
|---|---|---|
| Devuelve | HTML con un template | JSON, XML u otro formato |
| Valida | Form | Serializer |
| CRUD | Escribes cada vista | ViewSet + router generan rutas y acciones |
| Probar | Formulario en el navegador | API navegable + herramientas HTTP |
Proyecto, aplicación y configuración inicial.
Un entorno virtual aísla las dependencias de cada proyecto.
.venv? Cada proyecto tiene sus propias versiones; tu sistema queda limpio.manage.py quede en la raíz: menos anidamiento y comandos más cortos.taller/
├── manage.py
├── requirements.txt
└── taller/
├── __init__.py
├── settings.py
├── urls.py
├── asgi.py
└── wsgi.py
settings.pyapps, base de datos, idioma, zona horaria…urls.pyenruta cada URL a una vistaasgi.pyentrada ASGI (async) para producciónwsgi.pyentrada WSGI clásica para producciónmanage.pyscript de administración del proyectoEn taller/settings.py sumamos DRF y nuestra app a las aplicaciones instaladas.
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"cursos",
]
rest_framework habilita la API navegable; cursos se agrega al crearla (siguiente slide).cursos/
├── migrations/
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py
serializers.py: es una convención de DRF y lo crearemos nosotros./admin/panel listo para administrar usuarios y modeloscreatesuperuseracceso total al admin y a las apps registradasModelos, serializadores, vistas y routers.
Descripciónlos datos, en código Python, reflejados en tablas de la base de datos.SQL por detráscada consulta ejecuta SQL y devuelve objetos Python cómodos de usar.models.pycada aplicación declara ahí sus modelos.from django.contrib.auth.models import User
from django.db import models
class Categoria(models.Model):
nombre = models.CharField(max_length=20)
class Curso(models.Model):
titulo = models.CharField(max_length=100)
descripcion = models.TextField(blank=True)
categorias = models.ManyToManyField(Categoria, related_name="cursos")
creador = models.ForeignKey(
User, on_delete=models.CASCADE, null=True, blank=True
)
def __str__(self):
return self.titulo
on_delete es obligatorio desde Django 2.0, related_name="cursos" es más claro y agregamos __str__.Convierten objetos Python ⇄ JSON y validan lo que entra, como los Forms.
from rest_framework import serializers
from cursos.models import Categoria, Curso
class CategoriaSerializer(serializers.ModelSerializer):
class Meta:
model = Categoria
fields = ("id", "nombre")
class CursoSerializer(serializers.ModelSerializer):
class Meta:
model = Curso
fields = ("id", "titulo", "descripcion", "categorias", "creador")
creador como read_only_fields y asígnalo desde self.request.user: nadie debería crear cursos a nombre de otro.Entradareciben un objeto con la información de la petición (request).Salidadevuelven JSON (u otro formato) o una excepción si algo falla.views.pycada aplicación declara ahí sus vistas; en DRF suelen ser clases.| Acción | Método | Ruta generada | Qué hace |
|---|---|---|---|
list | GET | /cursos/ | Lista la colección |
create | POST | /cursos/ | Crea un recurso |
retrieve | GET | /cursos/{id}/ | Devuelve un recurso |
update | PUT | /cursos/{id}/ | Reemplaza el recurso |
partial_update | PATCH | /cursos/{id}/ | Edita parte del recurso |
destroy | DELETE | /cursos/{id}/ | Elimina el recurso |
from rest_framework import viewsets
from cursos.models import Categoria, Curso
from cursos.serializers import CategoriaSerializer, CursoSerializer
class CategoriaViewSet(viewsets.ModelViewSet):
queryset = Categoria.objects.all()
serializer_class = CategoriaSerializer
class CursoViewSet(viewsets.ModelViewSet):
queryset = Curso.objects.all()
serializer_class = CursoSerializer
queryset estaban cruzados (CategoriasView consultaba Curso). Cada vista debe apuntar a su propio modelo.El mismo CRUD de Curso, según el enfoque elegido.
Cifras ilustrativas para comparar enfoques; el código real varía según el proyecto.
Definir URLsgeneran las rutas del API de forma ordenada a partir de los ViewSets.Mapear verbosdeciden qué acción se ejecuta según el verbo HTTP y el path.Organizartodo en el urls.py del proyecto o uno por aplicación.DefaultRouter registramos cursos una vez y obtenemos las 6 rutas del CRUD, sin escribir ningún path() a mano.from django.contrib import admin
from django.urls import include, path
from rest_framework import routers
from cursos.views import CategoriaViewSet, CursoViewSet
router = routers.DefaultRouter()
router.register("cursos", CursoViewSet, basename="cursos")
router.register("categorias", CategoriaViewSet, basename="categorias")
urlpatterns = [
path("admin/", admin.site.urls),
path("api/v1/", include(router.urls)),
]
url() con regex pasó a path(), y la API se monta bajo /api/v1/ para poder versionarla.La API navegable permite hacer POST y PUT desde el propio navegador.
Clientes gráficos o de terminal para guardar colecciones de peticiones y compartirlas con el equipo.
CategoriaViewSet debe usar Categoria.objects.all(), no Curso. Síntoma: el endpoint devuelve datos del otro modelo.
on_deleteObligatorio en cada ForeignKey desde Django 2.0. Y si cambias el modelo: makemigrations + migrate.
rest_frameworkSin registrarlo en INSTALLED_APPS no hay API navegable ni routers.
Montar la API en /api/v1/ desde el inicio te deja evolucionar sin romper clientes.
Tu primera API es un modelo, un serializer, una vista y un router.