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 refresh rotado; 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.