Manejo de Errores en Express
Centraliza las respuestas de error con middleware de errores de cuatro argumentos y deja que Express 5 maneje las promesas rechazadas automáticamente.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import express, { type Request, type Response, type NextFunction } from "express";
class AppError extends Error {
constructor(
public statusCode: number,
message: string,
public code?: string
) {
super(message);
}
}
const app = express();
app.get("/users/:id", async (req, res) => {
const user = await findUser(req.params.id);
if (!user) throw new AppError(404, "User not found", "USER_NOT_FOUND");
res.json(user);
});
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
const status = err instanceof AppError ? err.statusCode : 500;
const code = err instanceof AppError ? err.code : "INTERNAL_ERROR";
res.status(status).json({ error: err.message, code });
});Cuándo usarlo: Cualquier API que devuelva formas de error consistentes en lugar de rastreos de pila HTML o solicitudes colgadas.
Ejemplo Funcional
import express, { type Request, type Response, type NextFunction } from "express";
import { z } from "zod";
class AppError extends Error {
constructor(public statusCode: number, message: string) {
super(message);
}
}
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
const app = express();
app.use(express.json());
function validateBody<T>(schema: z.ZodSchema<T>) {
return (req: Request, _res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
next(new AppError(422, result.error.message));
return;
}
req.body = result.data;
next();
};
}
app.post("/users", validateBody(createUserSchema), async (req, res) => {
const user = await createUser(req.body);
res.status(201).json(user);
});
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
console.error(err);
const status = err instanceof AppError ? err.statusCode : 500;
res.status(status).json({
error: status === 500 ? "Internal server error" : err.message,
});
});
async function createUser(data: z.infer<typeof createUserSchema>) {
return { id: 1, ...data };
}Lo que esto demuestra:
AppErrorpersonalizado con códigos de estado- Middleware de validación que llama a
next(err)en lugar de manejar en línea - El manejador asíncrono de Express 5 lanza errores reenviados al middleware de errores
- Mensajes 500 seguros para producción (sin fuga de pila)
Análisis Profundo
Cómo Funciona
- Los errores síncronos en los manejadores de rutas son capturados por Express
- Express 5 captura automáticamente las promesas rechazadas de los manejadores
async next(err)salta el middleware restante y va al primer manejador de 4 argumentos- El middleware de errores debe registrarse después de todas las rutas
Estándares de Respuesta de Error
| Estado | Significado | Ejemplo |
|---|---|---|
| 400 | Solicitud mal formada | JSON inválido |
| 401 | No autenticado | Token faltante |
| 403 | No autorizado | Token válido, rol incorrecto |
| 404 | No encontrado | ID de recurso desconocido |
| 422 | Fallo de validación | Rechazo de esquema Zod |
| 500 | Error del servidor | Excepción no manejada |
Notas de TypeScript
// Manejador de errores tipado
const errorHandler: express.ErrorRequestHandler = (err, _req, res, _next) => {
res.status(500).json({ error: "Internal server error" });
};
app.use(errorHandler);Errores comunes
- Olvidaste registrar el middleware de errores - Express por defecto envía páginas de error HTML. Solución: añade un manejador de 4 argumentos al final.
- Llamar a
res.json()y luego anext(err)- choque por doble respuesta. Solución: retorna después de enviar, nunca llames anextdespués de responder. next(err)en el middleware de errores - pasa al siguiente manejador de errores; llamar anext()sinerrsalta al middleware regular. Solución: finaliza la respuesta en el manejador de errores final.- Exponer
err.stacken producción - fuga de información. Solución: registra la pila en el servidor, envía un mensaje genérico al cliente. - No manejar 404 por separado - las rutas desconocidas pasan sin respuesta. Solución: añade un 404 general antes del manejador de errores.
- Express 4 asíncrono sin envoltorio - las promesas rechazadas no manejadas bloquean el proceso. Solución: actualiza a Express 5 o usa try/catch.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
express-async-errors (Express 4) | Atascado en Express 4 | Ya estás en Express 5 |
Fastify setErrorHandler | Aplicación Fastify con errores de esquema | Código base de Express |
| Filtros de excepción de NestJS | Excepciones HTTP basadas en decoradores | API Express simple |
| Tipo de resultado (neverthrow) | Manejo de errores funcional en servicios | El equipo prefiere throw/catch |
Preguntas Frecuentes
¿Express 5 todavía necesita express-async-errors?
No. Express 5 reenvía de forma nativa los errores asíncronos al middleware de errores.
¿Cómo manejo los errores operativos versus los errores de programador?
Los errores operativos (404, validación) obtienen mensajes específicos. Los errores de programador (TypeError) se registran completamente y devuelven un 500 genérico.
¿Los errores de validación deben devolver 400 o 422?
Ambos son comunes. Elige uno y documéntalo en tus estándares de API. 422 es popular para fallos de validación semántica.
¿Cómo pruebo el middleware de errores?
Supertest: request(app).get("/missing").expect(404) y verifica la forma del error JSON.
¿Qué pasa con las promesas rechazadas no manejadas fuera de las rutas?
Registra process.on("unhandledRejection") para registrar y apagar de forma segura. No confíes en Express para capturar esos.
¿Puedo tener varios manejadores de errores?
Sí. Llama a next(err) en el primero para pasarlo al siguiente. El último manejador siempre debe enviar una respuesta.
¿Cómo se integra esto con OpenAPI?
Documenta los esquemas de respuesta de error por código de estado. El middleware de errores debe coincidir con esas formas documentadas.
¿Debo usar `res.sendStatus(500)` en el manejador de errores?
Prefiere res.status(500).json({...}) para APIs. sendStatus devuelve texto plano.
Relacionado
- Orden de Middleware - ubicación del manejador de errores
- Migración a Express 5 - cambios en errores asíncronos
- Middleware de Seguridad - no filtres errores
- Estándares de Respuesta de Errores - formas de error de API
- Mejores Prácticas de Express - lista de verificación de la sección
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS Activo), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.