¿Qué hace Diseño de API REST?
Las APIs son productos: el modelo mental del consumidor, no tu esquema de base de datos, decide la forma. El error costoso que esta habilidad previene es lanzar una superficie inconsistente - nomenclatura mixta, códigos de estado incorrectos, paginación por desplazamiento en una tabla que crece - que luego cada consumidor hardcodea, haciéndolo efectivamente imposible de arreglar una vez integrado.
Procedimiento operativo
Trabaja en este orden. Los recursos vienen antes que los métodos porque la semántica del método depende de qué es el recurso; los contratos de error y paginación vienen antes de que se escriba la especificación porque adaptarlos después rompe a los consumidores.
Paso 1: recopilar información
Recolecta antes de diseñar. Etiqueta cualquier suposición como conjetura.
- Los consumidores: servicios internos, clientes de primera parte o desarrolladores externos. Los consumidores externos elevan el estándar de consistencia - asume externo si se desconoce.
- Los sustantivos centrales del dominio y cuáles son propiedad de cuáles (determina el anidamiento).
- Tamaños esperados de colecciones y crecimiento. Una lista que puede exceder ~10,000 filas descarta la paginación por desplazamiento.
- Proporción lectura/escritura y expectativas de latencia por endpoint.
- Modelo de autenticación (afecta la corrección de 401 vs 403).
Paso 2: nombrar recursos
- Sustantivos en plural: /users, /orders, /products.
- Anida un nivel para propiedad: /users/{id}/orders. No anides más de dos niveles - aplana con filtros en su lugar (/orders?user_id=... es mejor que /users/{id}/orders/{oid}/items/{iid}).
- Sin verbos en las rutas; el método HTTP es el verbo. Para acciones genuinas que no encajan en ningún verbo CRUD (por ejemplo, enviar una factura), modela un POST en un subrecurso: POST /invoices/{id}/send-attempts.
- Mayúsculas consistentes: kebab-case para segmentos de ruta de varias palabras, snake_case para campos JSON - elige una vez, aplica en todas partes.
Paso 3: asignar métodos y códigos de estado
- GET: lectura, idempotente, sin cuerpo de solicitud. 200 con cuerpo.
- POST: crear. Retorna 201 más una cabecera Location que apunte al nuevo recurso. Retorna la representación creada en el cuerpo para que el cliente se salte un GET posterior.
- PUT: reemplazo completo, idempotente. Los campos faltantes significan "limpiar", no "mantener".
- PATCH: actualización parcial (JSON Merge Patch o JSON Patch - indica cuál en la especificación).
- DELETE: eliminar, idempotente, retorna 204. Eliminar un recurso ya eliminado retorna 204 o 404 - elige uno y documéntalo.
- Errores: 400 para entrada mal formada, 401 para credenciales faltantes o inválidas, 403 para credenciales válidas sin permiso, 404 para recursos ausentes (también usa 404 en lugar de 403 cuando revelar la existencia es una filtración), 409 para conflictos de estado, 422 para entrada bien formada pero semánticamente inválida, 429 para límites de tasa (con Retry-After), 500 para fallos del servidor - nunca expongas trazas de pila ni identificadores internos.
Paso 4: fijar el contrato de error
Una forma para cada error, código legible por máquina primero:
{
"error": "validation_error",
"message": "El email es obligatorio",
"field": "email",
"request_id": "req_abc123"
}
El request_id debe registrarse en el servidor para que el soporte pueda correlacionar. El mensaje humano es para que los desarrolladores lean los registros, nunca para mostrarlo al usuario final.
Paso 5: fijar paginación y filtrado
- Paginación basada en cursor para cualquier colección que crezca: retorna un cursor opaco y una bandera has_more. La paginación por desplazamiento solo es aceptable para listas pequeñas, acotadas y orientadas a administradores - degrada linealmente y salta/duplica filas bajo escrituras concurrentes.
- Tamaño de página predeterminado 25, máximo 100. Rechaza tamaños de página por encima del máximo con 400, no los recortes silenciosamente.
- Filtrado y ordenación mediante parámetros de consulta: ?status=active&sort=created_at:desc. Documenta qué campos son filtrables; rechaza parámetros de filtro desconocidos con 400 en lugar de ignorarlos (ignorar silenciosamente oculta errores del cliente).
Paso 6: aplicar presupuestos de rendimiento y payload
- Presupuesto de latencia p95: 300 ms para endpoints interactivos, 1 s para informes/exportaciones. Si una operación no puede cumplir 1 s, hazla asíncrona: 202 Accepted más un recurso de estado para sondear.
- Presupuesto de payload de respuesta: mantén las respuestas típicas por debajo de 100 KB; cualquier cosa que rutinariamente supere 1 MB necesita paginación, selección de campos (?fields=) o un flujo de exportación dedicado.
- Versiona los cambios disruptivos en la ruta URL (/v1/). Los cambios aditivos (nuevos campos opcionales) no requieren un cambio de versión. Entrega la línea de tiempo de desaprobación a api-versioning-strategist.
Contraste trabajado
Mal:
POST /getUserOrders?userId=42&page=3
→ 200 { "data": [...], "error": null }
Verbo en la ruta, semántica GET en un POST, paginación por desplazamiento, 200 que envuelve un campo de error que obliga a los clientes a analizar el cuerpo para saber si la llamada falló.
Bien:
GET /users/42/orders?status=shipped&limit=25&cursor=eyJpZCI6OTh9
→ 200 { "orders": [...], "next_cursor": "eyJpZCI6MTIzfQ", "has_more": true }
→ 404 { "error": "not_found", "message": "El usuario 42 no existe", "request_id": "req_9f2" }
Ruta con sustantivo, anidamiento de propiedad, paginación por cursor, códigos de estado que transmiten el resultado.
Entregable
Produzca una especificación de endpoint que contenga: la tabla de recursos (ruta, métodos, códigos de estado por método), la forma de error compartida, el contrato de paginación (formato del cursor, valores predeterminados, máximo), la lista blanca de filtros y el presupuesto de latencia/payload por endpoint.
NO HACER
- No reflejes el esquema de la base de datos en la API - los consumidores se acoplan a internos que querrás cambiar.
- No retornes 200 para fallos con un cuerpo de error; los clientes y la monitorización se basan en códigos de estado.
- No uses paginación por desplazamiento en colecciones no acotadas - duplica y salta filas bajo escrituras y degrada linealmente.
- No anides rutas más de dos niveles; el anidamiento profundo codifica una ruta de acceso y prohíbe todas las demás.
- No ignores silenciosamente parámetros de consulta desconocidos - un filtro mal escrito que devuelve datos sin filtrar es un error de clase de fuga de datos.
Estándar de calidad
- Un consumidor podría implementar un cliente a partir de la especificación sin hacer una sola pregunta.
- Cada endpoint indica sus códigos de estado, incluidos los casos de error.
- Todos los endpoints de colección comparten un contrato de paginación; todos los errores comparten una forma.
- Conceptos idénticos tienen nombres idénticos en todas las rutas y payloads.
- Cada endpoint tiene un presupuesto de latencia explícito y los de >1 s son asíncronos.