Todos sabemos escribir endpoints. Lo difícil es mantener una API durante años, con más equipo, más tráfico y menos miedo a cambiarla. Esta guía ordena, en pasos concretos, las prácticas que separan un backend "que funciona" de uno que se puede operar con confianza.
1. Diseña por capas y responsabilidades
Dividir la lógica evita que los objetos de transporte (DTOs), el framework HTTP y la base de datos se mezclen. Una estructura mínima y que escala:
- Controlador — recibe la petición HTTP, la valida y devuelve la respuesta. No contiene reglas de negocio.
- Servicio — contiene la lógica de negocio y orquesta repositorios e integraciones.
- Repositorio — accede a la persistencia (SQL, NoSQL, cache). Es el único lugar que conoce el ORM.
- Puertos/adaptadores — aislan servicios externos (correo, pagos, colas) detrás de interfaces propias.
Así, si algún día cambias de ORM o de proveedor de pagos, el impacto queda localizado en una carpeta.
2. Valida toda la entrada antes de tocar los datos
Nunca confíes en el cliente. Valida tipos, rangos, formatos y permisos lo antes posible y con una librería declarativa (Zod, JSON Schema, Pydantic). Ejemplo en Node.js/TypeScript:
// src/users/schema.js
const userSchema = z.object({
name: z.string().trim().min(2).max(80),
email: z.string().email(),
age: z.number().int().min(13).max(120),
});
function createUser(payload) {
const data = userSchema.parse(payload); // lanza error 400 detallado
return userRepository.create(data);
}
Además de la validación de forma, aplica validación de autorización en cada recurso (¿puede este usuario hacer esto con ese objeto?).
3. Estandariza códigos de estado y errores
Usa la semántica HTTP correcta: 400 solicitud inválida, 401 no autenticado, 403 sin permisos, 404 no existe, 409 conflicto de estado, 422 entidad no procesable, 429 demasiadas peticiones y 500/503 para fallos del servidor.
Devuelve errores con forma consistente para que el frontend los interprete sin heurísticas:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Revise los campos indicados",
"details": [{ "field": "email", "reason": "formato inválido" }]
}
}
4. Seguridad desde el día uno
- HTTPS obligatorio (termina el TLS en el edge).
- Autenticación con tokens de vida corta y
refreshrotado; revocación por sesión. - Secretos en variables de entorno o gestor de secretos; jamás en el repositorio.
- Rate limiting por IP y por usuario; protección contra brute force.
- CORS con origen permitido y sin comodines innecesarios.
- Sanitiza y parametriza siempre las consultas para evitar inyecciones.
5. Base de datos: migraciones e índices
Versiona el esquema con migraciones (drizzle, flyway, alembic) y aplícalas de forma ordenada en CI/CD: la base de datos evoluciona igual que el código. Crea índices según los patrones reales de query y revisa los planes de ejecución; evita el problema N+1 usando joins o cargas por lotes (batch loading).
6. Observabilidad: logs, métricas y tracing
Un backend sin observabilidad es 50% apostar. Haz fetch o log estructurado (JSON) con un request_id por petición, exponde métricas (latencia, errores, uso de colas) y usa tracing distribuido cuando haya varios servicios. Define alertas antes del incidente, no después.
7. Entornos y despliegue
Sigue los 12 factores: configuración por variables de entorno, procesos sin estado y despliegues inmutables. Cada entorno (desarrollo, staging, producción) con su propia base y secretos, y un pipeline que promocione la misma imagen sin recompilar.
8. Checklist rápido
- Validación en el borde de la API, no en cada controlador.
- Errores con código, mensaje y detalles consistentes.
- Autenticación fuerte + rate limiting + HTTPS.
- Migraciones versionadas y backups probados.
- Logs con request_id, métricas y alertas activas.
- CI/CD que despliegue igual en staging y producción.