Express
Tutorial Express: Gestión de errores
Aprende a manejar errores en Express usando middleware de errores y respuestas estructuradas para mejorar la robustez de tus aplicaciones.
Aprende Express y certifícateMiddleware de errores
Express proporciona un mecanismo especializado para manejar errores a través de middleware específicos que se ejecutan cuando ocurre una excepción en la aplicación. A diferencia del middleware regular, el middleware de errores tiene una firma única con cuatro parámetros que permite a Express identificarlo automáticamente.
Definición y estructura básica
El middleware de errores en Express se define con cuatro parámetros obligatorios: err
, req
, res
y next
. Esta firma específica es lo que distingue este tipo de middleware del middleware regular:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Algo salió mal!');
});
Es fundamental que el middleware de errores se registre después de todas las rutas y middleware que puedan generar errores, ya que Express procesa el middleware en el orden en que se define.
Propagación de errores
Para que un error llegue al middleware de errores, debe ser propagado explícitamente usando la función next()
con el error como argumento:
app.get('/usuario/:id', (req, res, next) => {
const userId = req.params.id;
if (!userId || isNaN(userId)) {
const error = new Error('ID de usuario inválido');
error.status = 400;
return next(error); // Propaga el error al middleware de errores
}
// Simulación de operación que puede fallar
obtenerUsuario(userId)
.then(usuario => res.json(usuario))
.catch(next); // Propaga automáticamente errores de promesas
});
Manejo de errores síncronos y asíncronos
Express captura automáticamente los errores síncronos que ocurren en las rutas, pero los errores asíncronos requieren manejo explícito:
Errores síncronos (capturados automáticamente):
app.get('/error-sincrono', (req, res) => {
throw new Error('Error síncrono'); // Express lo captura automáticamente
});
Errores asíncronos (requieren manejo explícito):
app.get('/error-asincrono', (req, res, next) => {
setTimeout(() => {
try {
throw new Error('Error asíncrono');
} catch (err) {
next(err); // Debe propagarse manualmente
}
}, 100);
});
// Con async/await
app.get('/error-async-await', async (req, res, next) => {
try {
await operacionAsincrona();
res.json({ success: true });
} catch (error) {
next(error); // Propaga el error
}
});
Middleware de errores múltiple
Puedes definir múltiples middleware de errores para manejar diferentes tipos de errores o realizar diferentes acciones:
// Middleware para logging de errores
app.use((err, req, res, next) => {
console.error(`Error en ${req.method} ${req.path}:`, err.message);
next(err); // Pasa al siguiente middleware de errores
});
// Middleware para errores de validación
app.use((err, req, res, next) => {
if (err.name === 'ValidationError') {
return res.status(400).json({
error: 'Error de validación',
details: err.message
});
}
next(err); // Si no es error de validación, pasa al siguiente
});
// Middleware general de errores (debe ir al final)
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
error: status === 500 ? 'Error interno del servidor' : err.message
});
});
Manejo avanzado con información contextual
Un middleware de errores robusto puede incluir información adicional sobre el contexto del error:
app.use((err, req, res, next) => {
// Información contextual del error
const errorInfo = {
timestamp: new Date().toISOString(),
method: req.method,
url: req.originalUrl,
ip: req.ip,
userAgent: req.get('User-Agent'),
error: {
message: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
}
};
// Log del error con contexto
console.error('Error capturado:', errorInfo);
// Respuesta diferenciada según el entorno
if (process.env.NODE_ENV === 'production') {
res.status(err.status || 500).json({
error: 'Ha ocurrido un error en el servidor'
});
} else {
res.status(err.status || 500).json(errorInfo);
}
});
Integración con operaciones de base de datos
El middleware de errores es especialmente útil cuando trabajas con operaciones de base de datos que pueden fallar:
app.post('/productos', async (req, res, next) => {
try {
const nuevoProducto = await db.productos.create(req.body);
res.status(201).json(nuevoProducto);
} catch (error) {
// Personaliza el error según el tipo
if (error.code === 'ER_DUP_ENTRY') {
error.status = 409;
error.message = 'El producto ya existe';
}
next(error);
}
});
// El middleware de errores maneja todos los errores de DB
app.use((err, req, res, next) => {
if (err.code && err.code.startsWith('ER_')) {
return res.status(400).json({
error: 'Error de base de datos',
message: err.message
});
}
next(err);
});
El middleware de errores en Express proporciona un punto centralizado para manejar todas las excepciones de tu aplicación, permitiendo un control granular sobre cómo se procesan y responden los diferentes tipos de errores que pueden ocurrir durante la ejecución.
Respuestas de error estructuradas
Una vez que el middleware de errores captura las excepciones, es fundamental devolver respuestas consistentes y bien estructuradas que faciliten tanto la depuración como la integración con clientes frontend. Las respuestas estructuradas proporcionan información clara sobre qué ocurrió, por qué falló la operación y cómo el cliente puede proceder.
Estructura básica de respuestas de error
Una respuesta de error bien estructurada debe incluir información esencial de manera consistente. El formato más común incluye un código de estado HTTP apropiado junto con un cuerpo JSON que describe el error:
app.use((err, req, res, next) => {
const errorResponse = {
success: false,
error: {
type: err.name || 'Error',
message: err.message,
code: err.code || 'INTERNAL_ERROR'
},
timestamp: new Date().toISOString(),
path: req.originalUrl
};
res.status(err.status || 500).json(errorResponse);
});
Códigos de error personalizados
Implementar un sistema de códigos permite a los clientes identificar tipos específicos de errores sin depender únicamente de los mensajes de texto:
// Definición de códigos de error
const ERROR_CODES = {
VALIDATION_FAILED: 'VALIDATION_FAILED',
RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
UNAUTHORIZED_ACCESS: 'UNAUTHORIZED_ACCESS',
DUPLICATE_RESOURCE: 'DUPLICATE_RESOURCE',
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED'
};
// Middleware que asigna códigos según el tipo de error
app.use((err, req, res, next) => {
let errorCode = ERROR_CODES.INTERNAL_ERROR;
let statusCode = 500;
// Mapeo de errores a códigos específicos
if (err.name === 'ValidationError') {
errorCode = ERROR_CODES.VALIDATION_FAILED;
statusCode = 400;
} else if (err.message.includes('not found')) {
errorCode = ERROR_CODES.RESOURCE_NOT_FOUND;
statusCode = 404;
} else if (err.name === 'UnauthorizedError') {
errorCode = ERROR_CODES.UNAUTHORIZED_ACCESS;
statusCode = 401;
}
res.status(statusCode).json({
success: false,
error: {
code: errorCode,
message: err.message,
type: err.name
}
});
});
Respuestas diferenciadas por entorno
Las respuestas deben adaptarse al entorno de ejecución, proporcionando información detallada en desarrollo pero protegiendo datos sensibles en producción:
app.use((err, req, res, next) => {
const isDevelopment = process.env.NODE_ENV === 'development';
const baseResponse = {
success: false,
error: {
message: err.message,
code: err.code || 'INTERNAL_ERROR'
},
timestamp: new Date().toISOString()
};
// Información adicional solo en desarrollo
if (isDevelopment) {
baseResponse.error.stack = err.stack;
baseResponse.error.details = err.details;
baseResponse.debug = {
method: req.method,
url: req.originalUrl,
headers: req.headers,
body: req.body
};
}
res.status(err.status || 500).json(baseResponse);
});
Manejo de errores de validación
Los errores de validación requieren un formato específico que permita al cliente identificar exactamente qué campos fallaron y por qué:
// Función auxiliar para formatear errores de validación
function formatValidationErrors(validationError) {
const errors = {};
if (validationError.errors) {
Object.keys(validationError.errors).forEach(field => {
errors[field] = {
message: validationError.errors[field].message,
value: validationError.errors[field].value,
kind: validationError.errors[field].kind
};
});
}
return errors;
}
// Middleware específico para errores de validación
app.use((err, req, res, next) => {
if (err.name === 'ValidationError') {
return res.status(400).json({
success: false,
error: {
type: 'ValidationError',
message: 'Los datos proporcionados no son válidos',
code: 'VALIDATION_FAILED',
fields: formatValidationErrors(err)
}
});
}
next(err);
});
Respuestas con sugerencias de acción
Las respuestas estructuradas pueden incluir sugerencias sobre cómo resolver el problema, mejorando la experiencia del desarrollador:
app.use((err, req, res, next) => {
const errorResponse = {
success: false,
error: {
message: err.message,
code: err.code || 'INTERNAL_ERROR'
}
};
// Agregar sugerencias según el tipo de error
switch (err.code) {
case 'RESOURCE_NOT_FOUND':
errorResponse.suggestion = 'Verifica que el ID del recurso sea correcto';
break;
case 'VALIDATION_FAILED':
errorResponse.suggestion = 'Revisa los campos requeridos y sus formatos';
break;
case 'UNAUTHORIZED_ACCESS':
errorResponse.suggestion = 'Asegúrate de incluir un token de autenticación válido';
break;
case 'RATE_LIMIT_EXCEEDED':
errorResponse.suggestion = 'Espera unos minutos antes de realizar más solicitudes';
errorResponse.retryAfter = 60; // segundos
break;
}
res.status(err.status || 500).json(errorResponse);
});
Localización de mensajes de error
Para aplicaciones multiidioma, las respuestas pueden incluir mensajes localizados basados en las preferencias del cliente:
// Diccionario de mensajes de error
const errorMessages = {
es: {
VALIDATION_FAILED: 'Los datos proporcionados no son válidos',
RESOURCE_NOT_FOUND: 'El recurso solicitado no existe',
UNAUTHORIZED_ACCESS: 'No tienes permisos para acceder a este recurso'
},
en: {
VALIDATION_FAILED: 'The provided data is not valid',
RESOURCE_NOT_FOUND: 'The requested resource does not exist',
UNAUTHORIZED_ACCESS: 'You do not have permission to access this resource'
}
};
app.use((err, req, res, next) => {
const language = req.headers['accept-language']?.split(',')[0]?.split('-')[0] || 'en';
const messages = errorMessages[language] || errorMessages.en;
const errorResponse = {
success: false,
error: {
code: err.code || 'INTERNAL_ERROR',
message: messages[err.code] || err.message,
originalMessage: err.message // Mensaje técnico original
}
};
res.status(err.status || 500).json(errorResponse);
});
Respuestas con metadatos adicionales
En algunos casos, es útil incluir metadatos que ayuden al cliente a tomar decisiones sobre cómo proceder:
app.use((err, req, res, next) => {
const errorResponse = {
success: false,
error: {
message: err.message,
code: err.code || 'INTERNAL_ERROR'
},
meta: {
requestId: req.id || generateRequestId(),
timestamp: new Date().toISOString(),
version: process.env.API_VERSION || '1.0.0'
}
};
// Metadatos específicos según el tipo de error
if (err.code === 'RATE_LIMIT_EXCEEDED') {
errorResponse.meta.retryAfter = err.retryAfter || 60;
errorResponse.meta.limit = err.limit;
errorResponse.meta.remaining = 0;
}
if (err.code === 'VALIDATION_FAILED' && err.fields) {
errorResponse.meta.invalidFields = Object.keys(err.fields);
}
res.status(err.status || 500).json(errorResponse);
});
Las respuestas de error estructuradas transforman las excepciones en información útil que permite a los clientes manejar errores de manera inteligente, facilitando tanto la depuración durante el desarrollo como la creación de interfaces de usuario que respondan apropiadamente a diferentes situaciones de error.
Otras lecciones de Express
Accede a todas las lecciones de Express y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Expressjs
Introducción Y Entorno
Instalación De Express
Introducción Y Entorno
Estados Http
Routing
Métodos Delete
Routing
Parámetros Y Query Strings
Routing
Métodos Get
Routing
Ejercicios de programación de Express
Evalúa tus conocimientos de esta lección Gestión de errores con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.