This error occurs when your Express or Node.js application attempts to modify HTTP response headers after the response body has already been sent to the client. This typically happens when multiple responses are sent per request or when headers are set after res.send(), res.json(), or similar methods.
In Node.js HTTP servers, response headers must be sent to the client before the response body. Once the headers have been sent, you cannot modify them. This error occurs when code tries to set or modify headers (using res.setHeader(), res.writeHead(), etc.) after the response has already started being sent to the client via res.send(), res.json(), res.write(), or other response-sending methods. The HTTP protocol requires headers to be transmitted first, followed by the response body. Once this transmission begins, the headers are locked and cannot be changed.
Use return when calling res.send(), res.json(), or other response methods to prevent further code execution:
// WRONG
app.get('/user', (req, res) => {
res.json({ id: 1, name: 'John' });
res.setHeader('X-Custom', 'value'); // Error: headers already sent
});
// CORRECT
app.get('/user', (req, res) => {
res.json({ id: 1, name: 'John' });
return; // Prevents further execution
});
// OR more idiomatically
app.get('/user', (req, res) => {
return res.json({ id: 1, name: 'John' });
});Restructure conditional logic to guarantee only one response path is executed:
// WRONG - both paths execute
app.get('/data', (req, res) => {
if (req.query.format === 'json') {
res.json({ data: 'value' });
}
if (!req.query.format) {
res.send('No format specified');
}
});
// CORRECT - only one path executes
app.get('/data', (req, res) => {
if (req.query.format === 'json') {
return res.json({ data: 'value' });
}
if (!req.query.format) {
return res.send('No format specified');
}
res.status(400).send('Invalid format');
});Always call res.setHeader() or res.writeHead() before res.send(), res.json(), or res.write():
// WRONG
app.get('/data', (req, res) => {
res.send('Hello World');
res.setHeader('Content-Type', 'text/plain'); // Too late!
});
// CORRECT
app.get('/data', (req, res) => {
res.setHeader('Content-Type', 'text/plain');
res.send('Hello World');
});
// Also correct - headers implicitly set by json()
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello' }); // Sets Content-Type: application/json
});When using callbacks or promises, ensure responses are only sent once and in the correct order:
// WRONG - response could be sent twice
app.get('/user/:id', (req, res) => {
User.findById(req.params.id, (err, user) => {
if (err) {
res.status(500).send('Error finding user');
}
res.json(user); // Might execute even if error was sent
});
});
// CORRECT - early return prevents double responses
app.get('/user/:id', (req, res) => {
User.findById(req.params.id, (err, user) => {
if (err) {
return res.status(500).send('Error finding user');
}
return res.json(user);
});
});
// With async/await (cleaner)
app.get('/user/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
return res.json(user);
} catch (err) {
return res.status(500).send('Error finding user');
}
});Check the res.headersSent flag before attempting to modify headers in complex scenarios:
app.get('/data', (req, res) => {
// ... some processing ...
if (!res.headersSent) {
res.setHeader('X-Custom', 'value');
}
// Send response
res.json({ data: 'value' });
});
// In middleware
app.use((req, res, next) => {
// Only add custom header if response hasn't been sent yet
if (!res.headersSent) {
res.setHeader('X-Request-ID', generateId());
}
next();
});Ensure middleware that modifies headers is placed before middleware/routes that send responses:
// WRONG - header middleware comes after route that sends response
app.get('/data', (req, res) => {
res.send('Data');
});
app.use((req, res, next) => {
res.setHeader('X-Custom', 'value');
next();
});
// CORRECT - header middleware before routes
app.use((req, res, next) => {
res.setHeader('X-Custom', 'value');
next();
});
app.get('/data', (req, res) => {
res.send('Data');
});The HTTP specification defines that headers must be sent before the response body. In Node.js, once res.writeHead() is called (which happens implicitly in res.send(), res.json(), etc.), the headers are locked. Some frameworks track this with res.headersSent (boolean flag).
When piping streams, be careful with error handling - a pipe to the response can trigger multiple end events. Always check res.headersSent or use proper error event handlers on streams.
In Express error handlers, always send a response before calling next() to avoid double responses. Express error handlers have a different signature (4 parameters: err, req, res, next) - they should be registered last in your app.
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process