Fix Express.js 'ERR_HTTP_HEADERS_SENT' — Stop double responses
Response sent twice crashes Express. One route handler triggers res.send() or res.json() then hits another. Use `return` or guard early.
Quick answer
Add return before every res.send(), res.json(), res.end(), or res.redirect() call in your route handlers.
return res.status(200).json({ ok: true });What's actually happening here
Express.js uses Node's built-in HTTP response object. Once you call res.send() or res.json(), the headers get sent to the client immediately. If your code keeps running after that—maybe through an if/else block or a callback chain—and hits another res.send(), you get the ERR_HTTP_HEADERS_SENT error. The full error message reads: Cannot set headers after they are sent to the client.
This usually happens in four scenarios:
- Multiple conditional branches both send a response without a guard (like
elseorreturn). - An async callback or promise resolution fires after a synchronous response.
- Middleware calls
next()after sending a response. - You accidentally call
res.send()twice in the same function (e.g., from a loop).
The error is not an Express bug—it's your code continuing to execute after you've already closed the response. Node's HTTP module locks the socket after headers are sent. Trying to write more headers throws a TypeError that Express doesn't swallow.
Fix steps (in order of effectiveness)
- Add
returnbefore response calls. This is the most reliable fix.
Theapp.get('/user/:id', (req, res) => {
if (!req.params.id) {
return res.status(400).json({ error: 'Missing ID' });
}
return res.json({ id: req.params.id });
});returnstops the function dead. No more code runs. - Wrap conditionals with
elseblocks.
This ensures only one branch executes.if (err) {
res.status(500).send('Error');
} else {
res.send('Success');
} - Check if headers are already sent. Use
res.headersSentas a guard in rare cases where you can't refactor.
But don't rely on this as your main strategy—it hides the real bug.if (!res.headersSent) {
res.send('Final response');
} - Avoid calling
next()after sending a response. If you're in middleware that sends a response (like auth), don't callnext(). Justreturnafter sending.app.use((req, res, next) => {
if (!req.token) {
return res.status(401).send('Unauthorized');
}
next();
});
Alternative fixes if the main one fails
Sometimes the error persists even after adding return. Here's what's actually happening then:
- Async callbacks firing late. You attached a listener or callback that fires after the initial response. For example, an event listener on a stream that calls
res.write()orres.end()after the HTTP request already completed. Fix: remove the listener or guard withres.finishedorres.destroyed. - Promises that resolve after the response.
then()blocks that try to send data after the route already returned. The real fix here is to move all async work before sending the response, or useawaitproperly. - Express error handlers that re-send. If you have a custom error handler and also call
res.send()in the route, the error handler fires too. Usereturn next(err)in routes to let the error handler be the single response point.
Prevention tip
Adopt a strict pattern in your Express handlers: one response per route, and always return it. I write all my handlers like this:
app.get('/data', async (req, res) => {
try {
const data = await fetchData();
return res.json(data);
} catch (err) {
return res.status(500).json({ error: err.message });
}
});Notice: every res call has a return. No else blocks needed. The try/catch ensures only one code path sends a response. This pattern eliminates the error entirely.
Also, enable Express's 'error' event listener to catch unhandled errors that might cause double sends:
process.on('unhandledRejection', (err) => {
console.error('Unhandled Rejection:', err);
});Finally, if you're using a linter like ESLint, add the no-return-await rule and ensure your async functions don't return res.send() values improperly. The error is 100% preventable once you internalize this rule: after you send, you stop.
Was this solution helpful?