Skip to content

/errors/express/cannot-set-headers-after-they-are-sent

Express: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
Express
exit code
0
versions
Express 4 – 5, Node.js 12+
os
any

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client means your code tried to respond, or to set a header, on a request that already has a response. The first res.send() / res.json() / res.redirect() sent the status line and headers; the second call throws. Almost always the cause is a missing return after the first response, or a middleware that both responds and calls next().

Reproduce it

An Express 5 route that responds in an if without returning, with an error handler that prints the error:

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund express@5 >/dev/null 2>&1
cat > server.js <<'JS'
const express = require("express");
const app = express();

app.get("/users/:id", (req, res) => {
  if (req.params.id !== "1") {
    res.status(404).json({ error: "not found" }); // missing return
  }
  res.json({ id: 1, name: "Ada" });
});

app.get("/fixed/:id", (req, res) => {
  if (req.params.id !== "1") return res.status(404).json({ error: "not found" });
  res.json({ id: 1, name: "Ada" });
});

app.use((err, req, res, next) => {
  console.log(`${err.name} [${err.code}]: ${err.message}`);
  if (res.headersSent) return next(err);
  res.status(500).end();
});

const server = app.listen(4401, "127.0.0.1");
setTimeout(() => server.close(), 3000);
JS
node server.js 2>/dev/null &
sleep 1
r1=$(curl -s http://127.0.0.1:4401/users/2)
r2=$(curl -s http://127.0.0.1:4401/fixed/2)
wait
echo "client, /users/2: $r1"
echo "client, /fixed/2: $r2"

Output

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
client, /users/2: {"error":"not found"}
client, /fixed/2: {"error":"not found"}

The server logs the error for /users/2 only; the client still receives the first (404) response, because the second res.json() throws on the server. Without the custom handler, Express prints the same error with its stack trace to stderr.

Fixes

js
// 1. return when responding
app.get('/users/:id', async (req, res) => {
  const user = await db.find(req.params.id);
  if (!user) return res.status(404).json({ error: 'not found' });
  res.json(user);
});

// 2. middleware: respond OR call next(), not both
function requireAuth(req, res, next) {
  if (!req.user) return res.status(401).end();
  next();
}

// 3. error handler: hand off if a response already started
app.use((err, req, res, next) => {
  if (res.headersSent) return next(err);
  res.status(500).json({ error: 'internal error' });
});

The stack trace points to the second call. Search the handler (and every middleware before it) for code paths that respond more than once, including res.redirect(), res.render(), res.sendFile() and res.end().

Sources