Cannot Set Headers After They Are Sent to the Client

If you’re a seasoned Node.js developer, you’ve probably run into this frustrating error before:

“Cannot set headers after they are sent to the client.”

This issue commonly appears when working with frameworks like Express.js, NestJS, or libraries such as Axios and http-proxy-middleware . In this article, we’ll break down what causes this error, how to fix it across different tools, and best practices to prevent it from happening again. By the end, you’ll have a solid strategy for handling HTTP responses correctly in your Node.js applications.


🔍 What Does This Error Mean?

The root cause of this error lies in how HTTP responses work in Node.js. Once the first chunk of data is sent to the client, the headers are considered “sent” and locked. Any attempt to modify them afterward will result in an ERR_HTTP_HEADERS_SENT error .

For example, calling res.send() or res.json() sends both headers and body content. If you try to call another response method afterward—like res.status() or res.header()—you’ll trigger the error.

Here’s a simple case where this happens in Express.js:

app.get('/example', (req, res) => {
  res.send('First response');
  res.send('Second response'); // ❌ Throws: Cannot set headers after they are sent
});

To avoid this, always ensure only one response is sent per request cycle.


🧱 Common Scenarios & Fixes Across Frameworks

Let’s explore real-world scenarios where this error typically occurs—and how to resolve it in each context.

1. Express.js: Multiple Responses Sent

In Express, developers often accidentally send more than one response due to logic flow issues.

❌ Bad Example:

app.get('/user', (req, res) => {
  if (userNotFound) {
    res.status(404).send('User not found');
  }
  res.send(user); // ❌ Also sends response if user exists
});

✅ Fix: Use return to Exit Early

app.get('/user', (req, res) => {
  if (userNotFound) {
    return res.status(404).send('User not found');
  }
  res.send(user);
});

This ensures no further code runs after the response is sent .


2. MongoDB Integration: Asynchronous Response Handling

When integrating with MongoDB using the official driver , asynchronous operations must be handled carefully to avoid sending duplicate responses.

❌ Problematic Code:

app.get('/data', async (req, res) => {
  const results = await db.collection('users').find().toArray();
  res.json(results);
  res.send('Done'); // ❌ Triggers error
});

✅ Fixed with Proper Async Flow

app.get('/data', async (req, res) => {
  try {
    const results = await db.collection('users').find().toArray();
    return res.json(results);
  } catch (err) {
    return res.status(500).send('Database error');
  }
});

Always wrap database calls in try/catch blocks and use return to stop execution .


3. Axios: Chaining Requests Without Control

Using Axios for external API calls can lead to errors if you’re not careful with control flow.

❌ Risky Axios Pattern:

app.get('/fetch', (req, res) => {
  axios.get('https://api.example.com/data')
    .then(response => res.json(response.data))
    .catch(() => res.status(500).send('Error'));
  res.send('Request processed'); // ❌ Sends second response
});

✅ Corrected Version

app.get('/fetch', (req, res) => {
  axios.get('https://api.example.com/data')
    .then(response => res.json(response.data))
    .catch(() => res.status(500).send('Error'));
});

Avoid placing any code that could send a response outside the promise chain .


4. NestJS: Misusing @Res() Decorator

In NestJS, when manually managing the response object via @Res(), it’s easy to make the same mistake.

❌ Bad Practice:

@Get()
getData(@Res() res) {
  res.send('Success');
  res.send('Again?'); // ❌ Throws error
}

✅ Best Practice Using return

@Get()
getData(@Res() res) {
  if (!dataAvailable) {
    return res.status(404).send('Not found');
  }
  return res.send('Success');
}

Use early returns to prevent multiple writes to the response stream .


5. http-proxy-middleware: Header Manipulation After Stream Start

When using http-proxy-middleware, modifying headers after the proxy has started streaming the response will also throw this error.

✅ Solution: Ensure all header manipulations occur in the onProxyReq hook before the proxy sends the initial response:

const { createProxyMiddleware } = require('http-proxy-middleware');

app.use('/api', createProxyMiddleware({
  target: 'http://backend',
  onProxyReq: (proxyReq, req, res) => {
    proxyReq.setHeader('x-custom-header', 'value');
  }
}));

Never modify headers once the proxy has begun streaming the response .


6. Nuxt.js: Server Middleware Conflicts

In Nuxt.js server middleware or API routes, multiple res.send() calls can cause the same error.

❌ Faulty Nuxt.js API Route:

export default function (req, res) {
  res.send('Hello');
  res.send('World'); // ❌ Error
}

✅ Safe Alternative

export default function (req, res) {
  if (someCondition) {
    return res.send('Hello');
  }
  return res.send('World');
}

Use early returns to safely exit and avoid double-sending .


✅ Best Practices to Avoid This Error

  1. Always Return After Sending a Response
    Prevent further execution by using return res.send(...) instead of just res.send(...).

  2. Handle Promises and Async/Await Carefully
    Ensure no other response methods are called after an initial one is sent. Wrap in try/catch for safety.

  3. Validate Logic Paths
    Review conditionals to avoid unintentional multiple response paths.

  4. Use Logging for Debugging
    Add debug logs before sending responses to trace unexpected flows.

  5. Keep Middleware and Routes Clean
    Don’t mix logic that might send responses in middleware unless explicitly designed to do so.


📚 Additional Resources


🧠 Final Thoughts

The “Cannot set headers after they are sent to the client” error is a common but easily preventable issue in Node.js development. Whether you’re working with Express, NestJS, Axios, or Nuxt.js, understanding how HTTP responses are managed is key to writing robust and error-free applications.

By applying these patterns and best practices, you’ll reduce bugs, improve maintainability, and build more reliable backend systems.

📌 Pro Tip: Always treat HTTP response sending as a one-time operation — once it’s done, don’t attempt to write anything else to the response stream.

Got questions or want to share your own debugging stories? Leave a comment below! And if you found this guide helpful, feel free to share it with your fellow developers.