← Node & Express

Middleware

¿Cómo funciona el middleware de manejo de errores en Express y cómo lo reconoce el framework?

Ver respuesta — intenta responderla en voz alta primero

Un middleware de errores es una función con cuatro parámetros: (err, req, res, next). Express lo reconoce como manejador de errores precisamente porque declara cuatro parámetros, no por su nombre. Va registrado al final, después de todas las rutas y middleware normales. Se activa cuando en algún punto del pipeline se llama a next(err) (pasándole un error). En él respondes al cliente con un status de error (por ejemplo 500) y un JSON, sin exponer el stack trace al usuario.

En Express, los errores no se manejan uno por uno en cada ruta. Existe un mecanismo central: cuando algo sale mal, en vez de llamar a next() sin argumentos, llamas a next(err) pasándole el error. Ese solo hecho hace que Express se salte todos los middleware y rutas normales que falten, y salte directo a tu middleware de errores.

¿Cómo sabe Express que una función es un manejador de errores y no un middleware normal? Por el número de parámetros. Un middleware normal tiene tres: (req, res, next). Uno de errores tiene cuatro: (err, req, res, next). Express cuenta los parámetros de la función (usando fn.length) y, si son cuatro, la trata como manejador de errores. Por eso:

  • El orden y la cantidad de parámetros importa. Aunque no uses next dentro, debes declararlo para que sean cuatro. Si escribes solo (err, req, res), Express lo tratará como middleware normal y no funcionará como manejador de errores.
  • Debe ir al final de todo, después de las rutas. Si lo pones arriba, no atrapa nada, porque los errores viajan "hacia adelante" en el pipeline.

Dentro del manejador tú decides la respuesta. Lo recomendado:

  • Registrar el error completo en el servidor (con console.error o un logger) para que tú lo veas en los logs.
  • Responder al cliente un mensaje genérico y un status apropiado (400, 404, 500...). Nunca mandes el err.stack ni detalles internos al cliente: eso filtra información sensible sobre tu servidor y es un riesgo de seguridad.
const express = require("express");
const app = express();

app.use(express.json());

// Una ruta que provoca un error a propósito.
app.get("/reporte/:id", (req, res, next) => {
  const id = Number(req.params.id);

  if (Number.isNaN(id)) {
    // En vez de responder aquí, pasamos el error al pipeline con next(err).
    // Express saltará directo al middleware de errores del final.
    return next(new Error("El id debe ser un número"));
  }

  res.json({ id, estado: "ok" });
});

// Ruta que simula un fallo inesperado.
app.get("/explota", (req, res, next) => {
  try {
    throw new Error("Algo falló en la base de datos");
  } catch (err) {
    next(err); // enviamos el error al manejador central
  }
});

// MIDDLEWARE DE ERRORES.
// Tiene CUATRO parámetros (err, req, res, next): por eso Express lo
// reconoce como manejador de errores. Va SIEMPRE al final.
app.use((err, req, res, next) => {
  // 1) Registramos el error completo SOLO en el servidor (en los logs).
  console.error("Error capturado:", err.message);
  console.error(err.stack);

  // 2) Respondemos al cliente algo genérico, SIN el stack trace.
  res.status(500).json({
    error: "Ocurrió un error en el servidor",
  });
});

app.listen(3000, () => {
  console.log("Servidor en http://localhost:3000");
});

Declarar el manejador de errores con solo tres parámetros, como (err, req, res), quitando el next "porque no lo uso". Al hacerlo, Express cuenta tres parámetros y lo trata como un middleware normal, no como manejador de errores, así que nunca captura los errores. Aunque no vayas a usar next, tienes que declararlo para que sean cuatro. El otro error grave es mandarle al cliente el err.stack o el err.message crudo: eso expone rutas de archivos, versiones y detalles internos del servidor, y es una fuga de información.

"El manejador de errores en Express es un middleware especial con cuatro parámetros: (err, req, res, next). Express lo distingue de un middleware normal justamente por tener cuatro parámetros en lugar de tres; internamente revisa la aridad de la función. Lo registro al final de todo, después de las rutas. Se dispara cuando en cualquier parte del pipeline llamo a next(err) con un error: ahí Express se salta el resto y cae en este manejador. Dentro, registro el error completo en los logs del servidor para poder depurarlo, pero al cliente solo le devuelvo un status como 500 y un mensaje genérico en JSON. Nunca le expongo el stack trace, porque eso filtraría detalles internos y sería un problema de seguridad."

Reto rápido

¿Por qué este manejador de errores no captura nada, aunque una ruta llame a next(err)?

app.use((err, req, res) => {
  res.status(500).json({ error: "Falló el servidor" });
});
Ver respuesta

Porque solo tiene tres parámetros: (err, req, res). Express identifica a los manejadores de errores por tener cuatro parámetros (contando fn.length). Con tres, Express lo trata como un middleware normal y no lo invoca cuando se llama a next(err). La solución es agregar el cuarto parámetro aunque no se use: app.use((err, req, res, next) => { ... }).