Cuando uno se topa con el error Error: Can't set headers after they are sent to the client en Node.js, lo primero que muchos piensan es que están tratando de configurar mal los encabezados. Pero la verdad es que la mayoría de las veces, el problema no es qué estás enviando, sino cuántas veces lo haces y cuándo.
Este error aparece porque el protocolo HTTP es súper claro: por cada petición que llega al servidor, solo puede haber una única respuesta. Una vez que mandas esa respuesta —ya sea un JSON, un HTML, un redirect, lo que sea— los encabezados de esa respuesta ya se fueron. No puedes, ni debes, intentar mandar más datos o cambiar los encabezados después de eso. Es como mandar una carta y luego intentar cambiar la dirección cuando ya está en camino; simplemente no funciona.
En Express, cuando usas funciones como res.json(), res.send(), res.redirect() o res.render(), lo que estás haciendo es precisamente eso: enviar la respuesta y, de paso, finalizar los encabezados. El problema viene porque estas funciones no detienen la ejecución de tu código. La ruta sigue corriendo hasta que llega al final de la función o encuentra un return.
Esto me ha pasado revisando repos de clientes y proyectos míos al principio. Ves un código que, a primera vista, parece lógico, pero está intentando mandar dos respuestas sin querer. El escenario más común es con condicionales o dentro de callbacks de funciones asíncronas.
Mira este ejemplo, que es un clásico:
app.get('/api/company/:companyId', function(req, res) {
const { companyId } = req.params;
Company.findById(companyId).exec((err, company) => {
if (err) {
res.status(500).json(err);
} else if (!company) {
res.status(404).json();
}
res.status(200).json(company); // ¡Cuidado! Esto siempre se ejecuta
});
});
Aquí el tema es que si, por ejemplo, no se encuentra la empresa (!company), se envía una respuesta 404. Pero como no hay un return después de ese res.status(404).json(), el código sigue ejecutándose y llega a la línea res.status(200).json(company);. Ahí tienes tu segundo intento de mandar una respuesta, y ¡zas! el Can't set headers.
A mí, esto me frustra a veces porque la solución es tan simple que parece una sobreingeniería mental cuando uno la piensa mucho. No hay que darle más vueltas de las necesarias. La clave es asegurarse de que solo se envíe una respuesta por cada petición. Y la forma más directa de garantizarlo es usando return después de cada función que envía una respuesta.
Así es como se ve la solución para el ejemplo anterior:
app.get('/api/company/:companyId', function(req, res) {
const { companyId } = req.params;
Company.findById(companyId).exec((err, company) => {
if (err) {
return res.status(500).json(err); // Con return, la ejecución se detiene aquí
} else if (!company) {
return res.status(404).json(); // Y aquí también
}
return res.status(200).json(company); // Siempre con return, para ser consistente
});
});
Con el return, una vez que envías una respuesta, la ejecución de esa rama de código se detiene. Esto asegura que no haya un segundo intento de enviar encabezados.
Mi recomendación concreta:
Cada vez que uses una función de Express que envía una respuesta (res.json(), res.send(), res.redirect(), res.render(), etc.), acostúmbrate a ponerle un return delante. Es una práctica simple, pero que te va a ahorrar dolores de cabeza con este error. Te lo digo por experiencia, de hecho, me ha salvado varias veces de bugs que no eran obvios a primera vista.