error nodejs
Node.js ERR_HTTP_HEADERS_SENT
Understanding Node.js ERR_HTTP_HEADERS_SENT - cannot set or send headers after they have already been sent to the client.
What It Means
ERR_HTTP_HEADERS_SENT (or “Cannot set headers after they are sent to the client”) occurs when your code tries to send a response to the client more than once for a single request. Once res.send(), res.json(), res.end(), or res.redirect() is called, the HTTP headers are sent and you cannot modify them or send another response.
Common Causes
- Calling
res.send()orres.json()multiple times - Not using
returnafter sending a response in a conditional block - Async callback executing after a response was already sent
- Error handler sending a response after the route already did
- Middleware calling
next()after sending a response - Multiple code paths sending responses without proper guards
How to Fix
Use return after sending response
// Bad: no return, so both responses execute
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
res.status(404).json({ error: 'Not found' });
// Missing return! Code continues to res.json below
}
res.json(user); // ERR_HTTP_HEADERS_SENT!
});
// Good: return after sending response
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'Not found' });
}
res.json(user);
});
Fix async callback issues
// Bad: callback may fire after response already sent
app.get('/api/data', (req, res) => {
fetchFromAPI((err, data) => {
if (err) {
res.status(500).json({ error: err.message });
}
res.json(data); // Runs even after error response!
});
});
// Good: use return or else
app.get('/api/data', (req, res) => {
fetchFromAPI((err, data) => {
if (err) {
return res.status(500).json({ error: err.message });
}
res.json(data);
});
});
Fix middleware issues
// Bad: middleware sends response AND calls next()
app.use((req, res, next) => {
if (!req.headers.authorization) {
res.status(401).json({ error: 'Unauthorized' });
next(); // Don't call next() after sending a response!
}
next();
});
// Good: return after sending response
app.use((req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
Check if response has been sent
app.get('/api/data', async (req, res) => {
try {
const data = await fetchData();
if (!res.headersSent) {
res.json(data);
}
} catch (error) {
if (!res.headersSent) {
res.status(500).json({ error: 'Internal error' });
}
}
});
Fix forEach/loop with async
// Bad: multiple responses in a loop
app.get('/api/process', async (req, res) => {
const items = [1, 2, 3];
items.forEach(async (item) => {
const result = await processItem(item);
res.json(result); // Called 3 times!
});
});
// Good: process all items, send once
app.get('/api/process', async (req, res) => {
const items = [1, 2, 3];
const results = await Promise.all(items.map(processItem));
res.json(results); // Called once
});
Proper error handling pattern
// Wrap async routes to catch errors
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/api/data', asyncHandler(async (req, res) => {
const data = await getData();
res.json(data);
}));
// Global error handler (only one response)
app.use((err, req, res, next) => {
if (res.headersSent) {
return next(err); // Delegate to Express default handler
}
res.status(500).json({ error: 'Something went wrong' });
});
Related Errors
- Node.js ECONNREFUSED - Connection refused from a downstream service.
- Node.js ETIMEDOUT - Connection timeout, which may lead to double-response bugs.