Express.js
Modelo de errores de la API
Explica cómo diseñar un modelo estable de errores para una API Express.js con códigos públicos, detalles seguros, correlación y mapeo desde errores de dominio.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Express.js
Explica cómo diseñar un modelo estable de errores para una API Express.js con códigos públicos, detalles seguros, correlación y mapeo desde errores de dominio.
El contrato de error debe permitir que un cliente tome decisiones sin comparar mensajes humanos ni conocer excepciones internas.
Un error público combina:
status HTTP
+ código estable
+ explicación segura
+ detalles opcionales
+ identificador de correlaciónEl status describe la categoría; el código identifica el caso de negocio o plataforma.
Sin contrato, cada endpoint responde algo distinto:
{ "error": "Not found" }{ "message": "Something went wrong", "success": false }Los clientes terminan comparando textos, los logs no correlacionan y cambiar una traducción rompe integraciones.
{
"type": "https://api.example.com/problems/order-conflict",
"title": "La orden no puede confirmarse",
"status": 409,
"code": "ORDER_CONFLICT",
"requestId": "req_123",
"details": [
{
"path": "status",
"code": "INVALID_TRANSITION"
}
]
}RFC Problem Details es una base útil, no una obligación. Puedes extenderlo con code, requestId y detalles.
URI que identifica la clase del problema y puede apuntar a documentación. Debe ser estable.
Resumen humano. Puede localizarse y cambiar sin que el cliente dependa de él.
Repite el status HTTP para consumidores que almacenan el body.
Identificador machine-readable estable, por ejemplo ORDER_NOT_FOUND.
Explicación específica y segura de la ocurrencia.
Identificador o URI de esta ocurrencia. No debe exponer internals.
Permite correlacionar soporte, logs y trazas.
Errores por campo o contexto estructurado.
Los códigos deben representar decisiones relevantes para el consumidor:
VALIDATION_ERROR
AUTHENTICATION_REQUIRED
FORBIDDEN
ORDER_NOT_FOUND
ORDER_CONFLICT
RATE_LIMITED
DEPENDENCY_UNAVAILABLE
INTERNAL_ERRORNo crees un código distinto para cada mensaje interno. Tampoco uses nombres de clases técnicas como PrismaClientKnownRequestError.
{
"code": "VALIDATION_ERROR",
"details": [
{
"path": "items.0.quantity",
"code": "TOO_SMALL",
"message": "La cantidad debe ser mayor que cero"
}
]
}path ayuda a UI; code permite lógica; message es presentación. Limita cantidad y longitud para no amplificar payloads.
InvalidOrderInput
→ 422 / VALIDATION_ERROR
OrderNotFound
→ 404 / ORDER_NOT_FOUND
OrderAlreadyCancelled
→ 409 / ORDER_CONFLICT
DependencyTimeout
→ 503 / DEPENDENCY_UNAVAILABLEEl dominio no debería saber esos status. El adapter HTTP conserva el mapping.
No expongas:
El response público y el log interno son distintos.
Para login, responder mensajes distintos como “usuario no existe” y “password incorrecta” facilita enumeración. Un código común INVALID_CREDENTIALS puede ser más seguro.
Puedes añadir retryable, pero debe tener semántica real:
{
"code": "DEPENDENCY_UNAVAILABLE",
"retryable": true
}Aun así el cliente debe considerar método, idempotencia, Retry-After y presupuesto.
Cambios compatibles:
Cambios potencialmente incompatibles:
codedetails de array a objetoDocumenta qué campos son contractuales.
El backend puede devolver mensajes localizados, pero los clientes no deben analizarlos. Otra opción es devolver códigos y permitir que la UI traduzca.
Si utilizas Accept-Language, recuerda Vary cuando caches compartidas intervienen.
function problemResponse(problem: HttpProblem, requestId: string) {
return {
type: `https://api.example.com/problems/${problem.code.toLowerCase()}`,
title: problem.title,
status: problem.status,
code: problem.code,
requestId,
...(problem.details ? { details: problem.details } : {}),
};
}Devuelve una lista limitada y ordenada. No obligues al cliente a corregir uno por request si ya conoces varios.
No serialices recursivamente causas internas. Extrae contexto público.
No lo reenvíes directamente. Traduce a tu contrato.
Responde INTERNAL_ERROR y conserva detalle solo en logs.
Genera uno temprano; el error handler debe poder producirlo incluso si falla middleware posterior.
message en frontend.Prueba snapshots o assertions sobre:
Evita snapshots enormes que aceptan cambios incompatibles sin revisión.
Un formato detallado mejora clientes y soporte, pero aumenta superficie contractual. Define un núcleo pequeño y extensiones opcionales. Problem Details facilita interoperabilidad, aunque un formato propio también puede ser válido si está documentado.
code y title tienen responsabilidades distintas?code es estable y machine-readable; title es texto humano cambiante.404, recurso inexistente y 405 aplica este contrato a fallos de matching y existencia.