Definition
¿Alguna vez has recibido un mensaje de error de una API que solo decía “Algo salió mal” sin contexto adicional? Te quedas preguntándote: ¿Fue mi petición? ¿Está caído el servidor? ¿Debería reintentar? ¿Puedo arreglarlo? Un manejo de errores deficiente deja a los desarrolladores frustrados y a los usuarios confundidos, mientras que un buen manejo de errores convierte los fallos en información accionable.
El manejo de errores en APIs abarca las estrategias, patrones y prácticas para detectar, reportar y recuperarse de fallos. No se trata solo de capturar excepciones - se trata de diseñar tu sistema para fallar elegantemente, proporcionar retroalimentación significativa y permitir un diagnóstico rápido. Un sistema de manejo de errores bien diseñado distingue entre errores del cliente (la petición estaba mal), errores del servidor (algo se rompió internamente) y errores transitorios (intenta de nuevo en un momento).
El mejor manejo de errores sigue un principio simple: los errores deben ser tan útiles para el consumidor como las respuestas exitosas. Esto significa formatos de error estructurados (como Problem Details de RFC 7807), códigos de error consistentes, mensajes legibles por humanos, detalles legibles por máquinas, y correlation IDs para trazabilidad. Cuando los errores se manejan correctamente, se convierten en una característica, no en un bug - guían a los desarrolladores hacia el uso correcto de la API y ayudan a los operadores a diagnosticar problemas en segundos en lugar de horas.
Example
Fallos de Pago en Stripe: Cuando un pago falla en Stripe, no recibes solo “Pago fallido”. Recibes un error estructurado con type (card_error), code (card_declined), decline_code (insufficient_funds), message (“Tu tarjeta no tiene fondos suficientes”), y param (card). Los desarrolladores pueden manejar cada caso de forma diferente programáticamente, mientras muestran a los usuarios exactamente qué salió mal.
Rate Limiting de GitHub API: La API de GitHub devuelve 403 con headers que te dicen exactamente cuándo puedes reintentar (X-RateLimit-Reset), cuántas peticiones te quedan (X-RateLimit-Remaining), y por qué estás siendo limitado. Esto transforma un error en información accionable: espera 45 segundos y listo.
Errores de Servicios AWS: Las APIs de AWS devuelven errores con formato estructurado incluyendo ErrorCode, Message, RequestId (para trazabilidad), y frecuentemente un enlace a documentación. El RequestId es crucial - cuando contactas soporte de AWS, pueden rastrear exactamente qué pasó en su lado usando ese ID.
Degradación Elegante en Netflix: Cuando el servicio de recomendaciones de Netflix falla, los usuarios no ven un error - ven contenido popular genérico en su lugar. El error se maneja internamente, se registra con contexto completo, y la experiencia del usuario continúa. El manejo de errores aquí significa fallar de forma invisible para usuarios mientras se alerta a los operadores.
Errores de Conexión a Base de Datos: Una API bien diseñada distingue entre “base de datos temporalmente no disponible” (503 - reintenta después), “timeout de consulta” (504 - quizás simplifica tu petición), y “consulta inválida” (400 - arregla tu petición). Cada uno requiere diferente manejo por parte del cliente.
Analogy
La Sala de Emergencias del Hospital: Cuando llegas a urgencias, no te dicen simplemente “estás enfermo”. Te hacen triage con información específica: qué está mal, qué tan severo es, qué departamento debería atenderte, y cuáles son tus próximos pasos. Un número de espera (correlation ID) permite a cualquiera rastrear tu caso. El buen manejo de errores proporciona el mismo nivel de información estructurada y accionable.
El GPS que Recalcula Ruta: Cuando tu GPS encuentra un camino cerrado, no deja de funcionar. Detecta el problema, te informa qué pasó (“carretera cerrada adelante”), sugiere alternativas (“recalculando vía Autopista 7”), y actualiza su estado acordemente. El manejo de errores debería similarmente detectar, informar, sugerir y adaptarse.
El Error en el Pedido del Restaurante: Un buen restaurante maneja los errores de pedido elegantemente. El mesero no solo dice “hay un problema con tu pedido” - explican qué pasó (“se nos acabó el salmón”), ofrecen alternativas ("¿puedo sugerirte la trucha?"), y compensan si es necesario. El registro de cocina documenta el incidente para que el gerente lo revise después.
El Proceso de Cancelación de Vuelo: Las aerolíneas tienen procesos estructurados para cancelaciones de vuelos: comunicación clara (qué pasó), código de razón (clima, mecánico, tripulación), opciones (reprogramación, reembolso), y un número de referencia para rastrear tu caso. Compara esto con “vuelo cancelado, arréglalo tú mismo”.
Code Example
// Implementación de Problem Details RFC 7807
interface ProblemDetails {
type: string; // URI identificando tipo de error
title: string; // Resumen corto
status: number; // Código de estado HTTP
detail: string; // Explicación legible por humanos
instance: string; // URI de ocurrencia específica
correlationId: string;
timestamp: string;
errors?: ValidationError[];
}
interface ValidationError {
field: string;
code: string;
message: string;
}
// Middleware manejador de errores
class ApiErrorHandler {
handle(error: Error, req: Request, res: Response) {
const correlationId = req.headers['x-correlation-id'] || uuid();
// Registrar error completo con contexto
logger.error('Petición fallida', {
correlationId,
error: error.message,
stack: error.stack,
path: req.path,
method: req.method,
userId: req.user?.id
});
// Determinar tipo de error y construir respuesta
const problemDetails = this.toProblemDetails(error, correlationId);
res
.status(problemDetails.status)
.header('Content-Type', 'application/problem+json')
.header('X-Correlation-ID', correlationId)
.json(problemDetails);
}
private toProblemDetails(error: Error, correlationId: string): ProblemDetails {
if (error instanceof ValidationError) {
return {
type: 'https://api.example.com/errors/validation',
title: 'Error de Validación',
status: 400,
detail: 'Uno o más campos fallaron la validación',
instance: `/errors/${correlationId}`,
correlationId,
timestamp: new Date().toISOString(),
errors: error.errors
};
}
if (error instanceof NotFoundError) {
return {
type: 'https://api.example.com/errors/not-found',
title: 'Recurso No Encontrado',
status: 404,
detail: error.message,
instance: `/errors/${correlationId}`,
correlationId,
timestamp: new Date().toISOString()
};
}
if (error instanceof RateLimitError) {
return {
type: 'https://api.example.com/errors/rate-limit',
title: 'Demasiadas Peticiones',
status: 429,
detail: `Límite de tasa excedido. Reintenta después de ${error.retryAfter} segundos`,
instance: `/errors/${correlationId}`,
correlationId,
timestamp: new Date().toISOString()
};
}
// Por defecto: Error interno del servidor (ocultar detalles en producción)
return {
type: 'https://api.example.com/errors/internal',
title: 'Error Interno del Servidor',
status: 500,
detail: process.env.NODE_ENV === 'development'
? error.message
: 'Ocurrió un error inesperado',
instance: `/errors/${correlationId}`,
correlationId,
timestamp: new Date().toISOString()
};
}
}
// Uso en manejador de ruta
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await userService.findById(req.params.id);
if (!user) {
throw new NotFoundError(`Usuario ${req.params.id} no encontrado`);
}
res.json(user);
} catch (error) {
next(error); // Pasar al manejador de errores
}
});
Diagram
flowchart TB
subgraph Request["Petición Entrante"]
A[Petición del Cliente]
end
subgraph Handler["Clasificación de Error"]
B{¿Tipo de Error?}
C[Error de Cliente
4xx]
D[Error de Servidor
5xx]
E[Error Transitorio
Reintentable]
end
subgraph Response["Respuesta de Error"]
F[Problem Details
RFC 7807]
G[Header
Retry-After]
H[Correlation ID]
end
subgraph Logging["Observabilidad"]
I[Log Estructurado]
J[Actualizar Métricas]
K[Alertar si Crítico]
end
A --> B
B -->|Validación/Auth| C
B -->|Fallo Interno| D
B -->|Timeout/Sobrecarga| E
C --> F
D --> F
E --> F
E --> G
F --> H
H --> I
I --> J
J --> K
style C fill:#fca5a5
style D fill:#f87171
style E fill:#fcd34d
style F fill:#93c5fd
style I fill:#86efac
Security Notes
CRÍTICO - …
Configuración y Validación:
- Siempre sanitiza los mensajes de error antes de devolverlos a los clientes.
- Los errores internos deben registrar detalles completos en el servidor pero devolver mensajes genéricos a los usuarios.
- Usa correlation IDs para que soporte pueda buscar el error real sin exponerlo.
- Valida y sanitiza los correlation IDs recibidos de clientes para prevenir ataques de inyección en logs.
Monitoreo y Protección:
- Limita la tasa de endpoints de error para prevenir que atacantes busquen información a través de tu manejo de errores.
- Diferentes tipos de error pueden requerir diferentes tratamientos de seguridad.
- Los errores de autenticación deben ser vagos (“credenciales inválidas” no “usuario no encontrado” vs “contraseña incorrecta”) para prevenir enumeración de usuarios.
- Registra errores relevantes para seguridad separadamente para pistas de auditoría.
- Intentos de autenticación fallidos, fallos de autorización, y errores de validación que podrían indicar ataques deben ser marcados para revisión de seguridad..
Best Practices
- Usa formatos de error estructurados - Adopta Problem Details RFC 7807 para errores consistentes y legibles por máquinas
- Incluye correlation IDs - Cada error debe tener un ID único para rastrear a través de logs
- Distingue tipos de error - Errores de cliente (4xx) vs errores de servidor (5xx) vs errores transitorios (reintentables)
- Proporciona mensajes accionables - Dile al consumidor qué salió mal Y qué hacer al respecto
- Registra contexto completo internamente - Stack traces, datos de petición, contexto de usuario - pero nunca expongas a clientes
- Usa códigos de estado HTTP apropiados - No devuelvas 200 OK con un cuerpo de error
- Incluye guía de reintento - Para límites de tasa y errores transitorios, dile a los clientes cuándo reintentar
- Falla rápido - Detecta errores temprano y repórtalos inmediatamente en lugar de dejarlos cascadear
- Prueba los caminos de error - El código de manejo de errores necesita pruebas también - frecuentemente es el código menos probado
- Monitorea tasas de error - Rastrea errores como métricas para detectar problemas antes de que los usuarios los reporten
Common Mistakes
Filtrar información sensible: Stack traces, consultas SQL, rutas de archivos, o nombres de servicios internos en respuestas de error dan a los atacantes un mapa de tu sistema.
Mensajes de error genéricos: “Ocurrió un error” no ayuda a nadie. Incluso si ocultas detalles a los clientes, registra el contexto completo internamente.
Códigos de estado incorrectos: Usar 200 OK con cuerpo de error, 500 para errores de validación del cliente, o 400 para fallos del servidor confunde a los clientes y rompe convenciones.
Sin correlation IDs: Sin correlation IDs, rastrear un error a través de logs distribuidos se vuelve casi imposible.
No distinguir errores transitorios vs permanentes: Los clientes necesitan saber si deberían reintentar. Un error de validación no se arreglará reintentando; un timeout podría.
Tragarse errores: Capturar excepciones sin registrarlas o manejarlas hace imposible el debugging.
Formatos de error inconsistentes: Diferentes endpoints devolviendo errores en diferentes estructuras hace doloroso el manejo del lado del cliente.