Estructura de Rutas API
IDENTIFICAR: Cuándo activarse
Activar cuando:
- Se define cómo se comunica el frontend con el backend
- Se expone una API pública o interna
- Se añaden nuevos endpoints a una API existente
- El usuario dice "build an API" o "create a new endpoint"
REGLAS DE DISEÑO DE URL
Regla 1: Sustantivos, Nunca Verbos
✅ GET /api/users , listar usuarios
✅ POST /api/users , crear usuario
❌ POST /api/getUsers , verbo en URL
❌ GET /api/createUser , verbo en URL
Regla 2: Sustantivos en Plural para Colecciones
✅ /api/users
✅ /api/orders
❌ /api/user
❌ /api/order
Regla 3: Parámetros de Ruta para Recursos Específicos
✅ GET /api/users/abc123
✅ PATCH /api/users/abc123
✅ DELETE /api/users/abc123
Regla 4: Máximo 1 Nivel de Anidamiento
✅ GET /api/users/abc123/orders
❌ GET /api/users/abc123/orders/xyz789/items/def456
Para anidamiento más profundo, consultar el sub-recurso directamente: GET /api/items/def456
Regla 5: Parámetros de Consulta para Filtrado, Ordenamiento, Paginación
GET /api/users?status=active&sort=created_at&page=2&limit=20
Semántica de Métodos HTTP
| Método | Acción | Ejemplo | ¿Idempotente? | ¿Cuerpo de solicitud? |
|---|
GET | Listar / Leer | GET /api/users | Sí | No |
POST | Crear | POST /api/users | No | Sí |
PATCH | Actualizar parcialmente | PATCH /api/users/:id | No | Sí |
PUT | Reemplazar completamente | PUT /api/users/:id | Sí | Sí |
DELETE | Eliminar | DELETE /api/users/:id | Sí | No |
Envoltorio de Respuesta: SIEMPRE Consistente
Éxito (Colección)
{
"data": [{ "id": "1", "name": "Alice" }],
"meta": { "total": 42, "page": 1, "limit": 20 }
}
Éxito (Recurso Único)
{
"data": { "id": "1", "name": "Alice" }
}
Error: Formato RFC 9457 Problem Details
{
"error": { "message": "User not found", "status": 404 }
}
Selección de Códigos de Estado
| Código | Usar para | Nunca usar para |
|---|
200 OK | GET, PATCH, PUT exitosos | Errores (no devolver 200 con cuerpo de error) |
201 Created | POST exitoso | Cualquier otra cosa |
204 No Content | DELETE exitoso | Respuestas con cuerpo |
400 Bad Request | Fallo de validación, entrada malformada | Errores del servidor (usar 500) |
401 Unauthorized | Credenciales faltantes/inválidas | Errores de permiso (usar 403) |
403 Forbidden | Credenciales válidas, permisos insuficientes | Credenciales faltantes (usar 401) |
404 Not Found | El recurso no existe | Errores de validación (usar 400) |
409 Conflict | Recurso duplicado, conflicto de estado | Errores de validación (usar 400) |
422 Unprocessable | Fallo de validación semántica | Errores de análisis sintáctico (usar 400) |
429 Too Many Requests | Límite de tasa excedido | Errores de autenticación |
500 Internal Server Error | Fallo inesperado del servidor | Errores esperados (usar 4xx) |
Paginación: SIEMPRE Requerida
Cada endpoint de colección DEBE soportar paginación.
Basada en Cursor (PREFERIDA: estable bajo alto volumen de inserciones)
GET /api/users?cursor=abc123&limit=20
Respuesta: { "data": [...], "meta": { "next_cursor": "def456", "has_more": true } }
Basada en Desplazamiento (aceptable para conjuntos de datos pequeños y estables)
GET /api/users?page=1&limit=20
Respuesta: { "data": [...], "meta": { "total": 100, "page": 1, "limit": 20 } }
REGLAS:
- Límite por defecto: 20
- Límite máximo: 100
- SIEMPRE devolver conteo total (desplazamiento) o bandera has_more (cursor)
Implementación CRUD: SIEMPRE Implementar TODAS las Operaciones
Para cada recurso, implementar LAS SEIS operaciones. Nunca detenerse en GET y POST:
router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
router.put('/:id', replaceUser);
router.patch('/:id', updateUser);
router.delete('/:id', deleteUser);
Patrones Específicos por Stack
Next.js 16 Route Handlers (App Router)
Enrutamiento basado en archivos:
app/api/users/route.ts → GET /api/users, POST /api/users
app/api/users/[id]/route.ts → GET /api/users/:id, PATCH, DELETE
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const users = await db.user.findMany({ skip: (page - 1) * limit, take: limit });
const total = await db.user.count();
return NextResponse.json({ data: users, meta: { total, page, limit } });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const validated = .(body);
user = db..({ : validated });
.({ : user }, { : });
}
Express
import { Router } from 'express';
const router = Router();
router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
router.put('/:id', replaceUser);
router.patch('/:id', updateUser);
router.delete('/:id', deleteUser);
FastAPI
from fastapi import APIRouter, Query
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def list_users(page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100)):
pass
@router.post("/", status_code=201)
async def create_user(body: UserCreate):
pass
@router.get("/{user_id}")
async def get_user(user_id: str):
pass
VALIDAR: Puertas de Calidad
ANTIPATRONES: SIEMPRE Evitar
| Antipatrón | Por qué está mal | Solución |
|---|
POST /api/getUsers | Estilo RPC. Rompe la semántica HTTP. Confunde el caché. | GET /api/users |
200 OK con cuerpo de error | Engañoso. El cliente verifica el estado, no el cuerpo. | Devolver el código 4xx correcto |
/api/users/:uid/posts/:pid/comments/:cid | Anidamiento ilegible y frágil | GET /api/comments/:cid |
| 10,000 filas, sin paginación | Se agota el tiempo, bloquea clientes, agota la memoria | Siempre paginar. Por defecto 20, máximo 100. |
| Solo GET + POST implementados | Faltan PUT/PATCH/DELETE | Implementar LAS SEIS operaciones CRUD |
SALIDA: Qué Produce Esta Habilidad
{
"resources": [{ "name": "string", "endpoints": ["GET /", "POST /", "GET /:id", "PUT /:id", "PATCH /:id", "DELETE /:id"] }],
"pagination": "cursor | offset",
"envelope": { "success": "{ data, meta }", "error": "{ error: { message, status } }" },
"framework": "express | nextjs | fastapi | django"
}