Error: Can't set headers after they are sent to the client
The res
object in Express is a subclass of Node.js's http.ServerResponse
(read the http.js source). You are allowed to call res.setHeader(name, value)
as often as you want until you call res.writeHead(statusCode)
. After writeHead
, the headers are baked in and you can only call res.write(data)
, and finally res.end(data)
.
The error "Error: Can't set headers after they are sent." means that you're already in the Body or Finished state, but some function tried to set a header or statusCode. When you see this error, try to look for anything that tries to send a header after some of the body has already been written. For example, look for callbacks that are accidentally called twice, or any error that happens after the body is sent.
In your case, you called res.redirect()
, which caused the response to become Finished. Then your code threw an error (res.req
is null
). and since the error happened within your actual function(req, res, next)
(not within a callback), Connect was able to catch it and then tried to send a 500 error page. But since the headers were already sent, Node.js's setHeader
threw the error that you saw.
Comprehensive list of Node.js/Express response methods and when they must be called:
Response must be in Head and remains in Head:
res.writeContinue()
res.statusCode = 404
res.setHeader(name, value)
res.getHeader(name)
res.removeHeader(name)
-
res.header(key[, val])
(Express only) -
res.charset = 'utf-8'
(Express only; only affects Express-specific methods) -
res.contentType(type)
(Express only)
Response must be in Head and becomes Body:
res.writeHead(statusCode, [reasonPhrase], [headers])
Response can be in either Head/Body and remains in Body:
res.write(chunk, encoding='utf8')
Response can be in either Head/Body and becomes Finished:
res.end([data], [encoding])
Response can be in either Head/Body and remains in its current state:
res.addTrailers(headers)
Response must be in Head and becomes Finished:
-
return next([err])
(Connect/Express only) - Any exceptions within middleware
function(req, res, next)
(Connect/Express only) -
res.send(body|status[, headers|status[, status]])
(Express only) -
res.attachment(filename)
(Express only) -
res.sendfile(path[, options[, callback]])
(Express only) -
res.json(obj[, headers|status[, status]])
(Express only) -
res.redirect(url[, status])
(Express only) -
res.cookie(name, val[, options])
(Express only) -
res.clearCookie(name[, options])
(Express only) -
res.render(view[, options[, fn]])
(Express only) -
res.partial(view[, options])
(Express only)
I ran into this error as well for a while. I think (hope) I've wrapped my head around it, wanted to write it here for reference.
When you add middleware to connect or express (which is built on connect) using the app.use
method, you're appending items to Server.prototype.stack
in connect (At least with the current npm install connect
, which looks quite different from the one github as of this post). When the server gets a request, it iterates over the stack, calling the (request, response, next)
method.
The problem is, if in one of the middleware items writes to the response body or headers (it looks like it's either/or for some reason), but doesn't call response.end()
and you call next()
then as the core Server.prototype.handle
method completes, it's going to notice that:
- there are no more items in the stack, and/or
- that
response.headerSent
is true.
So, it throws an error. But the error it throws is just this basic response (from the connect http.js
source code:
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Cannot ' + req.method + ' ' + req.url);
Right there, it's calling res.setHeader('Content-Type', 'text/plain');
, which you are likely to have set in your render
method, without calling response.end(), something like:
response.setHeader("Content-Type", "text/html");
response.write("<p>Hello World</p>");
The way everything needs to be structured is like this:
Good Middleware
// middleware that does not modify the response body
var doesNotModifyBody = function(request, response, next) {
request.params = {
a: "b"
};
// calls next because it hasn't modified the header
next();
};
// middleware that modify the response body
var doesModifyBody = function(request, response, next) {
response.setHeader("Content-Type", "text/html");
response.write("<p>Hello World</p>");
response.end();
// doesn't call next()
};
app.use(doesNotModifyBody);
app.use(doesModifyBody);
Problematic Middleware
var problemMiddleware = function(request, response, next) {
response.setHeader("Content-Type", "text/html");
response.write("<p>Hello World</p>");
next();
};
The problematic middleware sets the response header without calling response.end()
and calls next()
, which confuses connect's server.
Some of the answers in this Q&A are wrong. The accepted answer is also not very "practical", so I want to post an answer that explains things in simpler terms. My answer will cover 99% of the errors I see posted over and over again. For the actual reasons behind the error take a look at the accepted answer.
HTTP uses a cycle that requires one response per request. When the client sends a request (e.g. POST or GET) the server should only send one response back to it.
This error message:
Error: Can't set headers after they are sent.
usually happens when you send several responses for one request. Make sure the following functions are called only once per request:
res.json()
res.send()
res.redirect()
res.render()
(and a few more that are rarely used, check the accepted answer)
The route callback will not return when these res functions are called. It will continue running until it hits the end of the function or a return statement. If you want to return when sending a response you can do it like so: return res.send()
.
Take for instance this code:
app.post('/api/route1', function(req, res) {
console.log('this ran');
res.status(200).json({ message: 'ok' });
console.log('this ran too');
res.status(200).json({ message: 'ok' });
}
When a POST request is sent to /api/route1 it will run every line in the callback. A Can't set headers after they are sent error message will be thrown because res.json()
is called twice, meaning two responses are sent.
Only one response can be sent per request!
The error in the code sample above was obvious. A more typical problem is when you have several branches:
app.get('/api/company/:companyId', function(req, res) {
const { companyId } = req.params;
Company.findById(companyId).exec((err, company) => {
if (err) {
res.status(500).json(err);
} else if (!company) {
res.status(404).json(); // This runs.
}
res.status(200).json(company); // This runs as well.
});
}
This route with attached callback finds a company in a database. When doing a query for a company that doesn't exist we will get inside the else if
branch and send a 404 response. After that, we will continue on to the next statement which also sends a response. Now we have sent two responses and the error message will occur. We can fix this code by making sure we only send one response:
.exec((err, company) => {
if (err) {
res.status(500).json(err);
} else if (!company) {
res.status(404).json(); // Only this runs.
} else {
res.status(200).json(company);
}
});
or by returning when the response is sent:
.exec((err, company) => {
if (err) {
return res.status(500).json(err);
} else if (!company) {
return res.status(404).json(); // Only this runs.
}
return res.status(200).json(company);
});
A big sinner is asynchronous functions. Take the function from this question, for example:
article.save(function(err, doc1) {
if (err) {
res.send(err);
} else {
User.findOneAndUpdate({ _id: req.user._id }, { $push: { article: doc._id } })
.exec(function(err, doc2) {
if (err) res.send(err);
else res.json(doc2); // Will be called second.
})
res.json(doc1); // Will be called first.
}
});
Here we have an asynchronous function (findOneAndUpdate()
) in the code sample. If there are no errors (err
) findOneAndUpdate()
will be called. Because this function is asynchronous the res.json(doc1)
will be called immediately. Assume there are no errors in findOneAndUpdate()
. The res.json(doc2)
in the else
will then be called. Two responses have now been sent and the Can't set headers error message occurs.
The fix, in this case, would be to remove the res.json(doc1)
. To send both docs back to the client the res.json()
in the else could be written as res.json({ article: doc1, user: doc2 })
.
I had this same issue and realised it was because I was calling res.redirect
without a return
statement, so the next
function was also being called immediately afterwards:
auth.annonymousOnly = function(req, res, next) {
if (req.user) res.redirect('/');
next();
};
Which should have been:
auth.annonymousOnly = function(req, res, next) {
if (req.user) return res.redirect('/');
next();
};