Back to blog

How to Fix Cannot Set Headers After They Are Sent to Client in Express

When building REST APIs using Node.js and Express, one of the most common server-side runtime crashes is: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.

This error causes your Node.js application process to crash if not caught globally.

Unlike syntax errors, this crash is a logic flaw: your server code attempted to send an HTTP response back to the client multiple times for a single incoming request.

In this guide, we will analyze why Express blocks multiple responses, examine common coding mistakes, and learn how to secure your control flows using return statements.

The Core Cause: The Single Response Rule

The HTTP protocol operates on a strict Request-Response cycle. For every single HTTP request initiated by a client, the server can return exactly one response.

An HTTP response consists of two parts:

  1. The Headers: Metadata containing the HTTP status code (e.g., 200 OK, 400 Bad Request) and content types.
  2. The Body: The actual HTML page or JSON data.

When you execute an Express response method (such as res.send(), res.json(), res.redirect(), or res.end()), Express immediately compiles and sends the HTTP headers down the network socket to the client.

Once those headers are transmitted, the response cycle is finalized. If your code subsequently attempts to modify headers (e.g., setting a status code) or send another payload, Node.js blocks the action and throws ERR_HTTP_HEADERS_SENT.

Common Coding Mistakes

Scenario A: Missing "return" in If-Else Blocks (Logical Bleeding)

This is the most common mistake. You validate user input, detect an error, send a 400 Bad Request response, but forget to halt execution. The code continues running to the end of the function:

// AVOID: Missing return statement
app.post('/api/users', (req, res) => {
  const { email } = req.body;

  if (!email) {
    // Express sends headers here
    res.status(400).json({ error: 'Email is required' }); 
  }

  // Code continues running!
  // Node.js attempts to send headers a SECOND time, crashing the server
  res.status(200).json({ message: 'Success' });
});
  • The Fix: Add the return keyword before sending error responses. This terminates function execution immediately:
// Good: Using return to halt execution
app.post('/api/users', (req, res) => {
  const { email } = req.body;

  if (!email) {
    return res.status(400).json({ error: 'Email is required' });
  }

  return res.status(200).json({ message: 'Success' });
});

Scenario B: Asynchronous Callbacks or Loops

If your handler queries a database inside a loop or has asynchronous callbacks, a fallback response might execute before the database resolves, or multiple callbacks might trigger responses:

// AVOID: Multiple responses in loop callbacks
users.forEach((user) => {
  if (user.isAdmin) {
    res.json({ role: 'Admin' }); // Might execute multiple times!
  }
});
  • The Fix: Structure loops to gather results first, or use async/await to control execution flow before responding.

Scenario C: Middleware calling next() after res.send()

If an Express middleware handler sends a response, it must never call next(). Calling next() routes the request to the next controller, which will attempt to send its own response:

// AVOID: Middleware sending response and continuing route
function checkAdmin(req, res, next) {
  if (!req.user.isAdmin) {
    res.status(403).send('Unauthorized');
  }
  next(); // Passes request to next router handler, leading to crash!
}
  • The Fix: Use return or else blocks to ensure next() is only invoked if no response was sent:
// Good: next() is only called if authorization passes
function checkAdmin(req, res, next) {
  if (!req.user.isAdmin) {
    return res.status(403).send('Unauthorized');
  }
  return next();
}

Conclusion

The ERR_HTTP_HEADERS_SENT crash occurs when backend code attempts to respond twice to a single client request. To prevent this, prefix all validation and error responses with the return keyword to exit handlers immediately, structure middleware to avoid calling next() after sending responses, and coordinate asynchronous callbacks to ensure only one response method executes.