Error: can't set headers after they are sent - node.js

I want to send an argument with res.redirect(). However, I'm getting an error while running it, saying that I cannot set headers after they are sent.
What does that mean, and how can I fix it?
app.post('/updateCollaborateRequest', function(req,res) {
if(req.body.accept == true) {
Team.findOne({'name': req.body.data.teamName}, function (err, team) {
if(err) {
res.redirect('/explore');
}
team.accepted = true;
team.save(function (err) {
if (err) {
alert(err);
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
});
});
res.redirect("/teamprof/" + team.name);
});
});
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
res.render('userprof1', {message : req.flash('done')});
});
});
});

Your code is continuing after redirecting. That is probably the problem. You should return, otherwise you are going to keep trying to write to the HTTP response.

This particular error message is caused by code paths that lead to multiple res.xxx() calls that try to send the response more than once.
You have multiple places where you are doing that. For example, you have two res.redirect() calls inside the Team.findOne() callback, but then you proceed with Request.findOne() where you have a res.render(). You HAVE to make sure that you only send the response once.
I'm not entirely sure what the desired logic is in all cases, but you can fix that error by adding an else statement before the Request.findOne() and adding a return after each res.redirect(). If this is not the exactly flow you want, then please explain more about how you want the control flow to work. Here's the code with those changes applied:
app.post('/updateCollaborateRequest', function(req,res) {
if(req.body.accept == true) {
Team.findOne({'name': req.body.data.teamName}, function (err, team) {
if(err) {
res.redirect('/explore');
return;
}
team.accepted = true;
team.save(function (err) {
if (err) {
// FIXME: need error handling here
alert(err);
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
// FIXME: need error handling here
console.log(err);
}
});
});
// Are you sure you want to send this response before
// you even know if the `Request.findOne()` and `request.save()`
// have been sucessful?
res.redirect("/teamprof/" + team.name);
return;
});
});
} else {
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
res.render('userprof1', {message : req.flash('done')});
});
});
}
});
You still have several error conditions for which no response is sent which is incomplete error handling so those need to be fixed too. And, I've added some comments in the code about some other suspect things in the code.

Related

Node: simulating network failure - res.status(404) not working

class gameInfo {
static async gameeee(req, res) {
try {
console.log(req.body);
await db.adb
.collection("game")
.findOne({ req.body.gameID}, async (err, result) => {
console.log("a");
if (err) {
console.log("b");
res.status(400);
} else if (result === null) {
console.log("c"); <------- this is called
res.status(404); <------ not happening
} else if (result !== null) {
res.json({ result });
}
});
} catch (err) {
console.log(err);
res.status(400);
}
}
}
console result is
a
c
I am trying to simulate the response failure due to no data. However, res.status(404) is not working. How can I send the error?
Also, I am super confused with among res.send, res.status and res.sendStatus. What are the differences using these three?
You still need to send or end the response, see https://expressjs.com/en/api.html#res.status:
res.status(404).end();
And yes, as the documentation says, you could just use sendStatus instead.
res.sendStatus(404) // equivalent to res.status(404).send('Not Found')

How to exit after response in nodejs with express?

This is my first time asking a question on stackoverflow. Sorry if I made posting mistakes.
I am trying to exit a function after sending a response to prevent continuing through the function.
node -v = v12.6.0
express = ^4.17.1
mongoose = ^5.6.6
// handle adding a new book request submission
addNewBook: function (req, res) {
var response = null;
var name = req.body.name.toLowerCase();
var url = req.body.url.toLowerCase();
var category = req.body.category.toLowerCase();
var tags = req.body.tags.toLowerCase();
// checking if category already exist. if not, make a new category
Category.find({label: category}).exec(function(err, data) {
if(err) {
response = res.status(400).send({message:'Error finding category.'});
} else if(data.length === 0) {
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
response = res.status(400).send({message:'Error saving new category.'});
}
})
}
});
// checking if book name already exist
Book.find({name: name}).exec(function(err, data){
if(err) {
response = res.status(400).send({message:'Error validating Book existence'});
} else if(data.length > 0) {
response = res.status(200).send({message:'book name already exist'});
} else {
req.body.name = name;
req.body.url = url;
req.body.category = category;
req.body.tags = tags;
// make a new book document
var newBook = new Book(req.body);
newBook.save(function (err, data) {
if (err) {
response = res.status(400).send({message: 'Error saving new Book.'});
} else {
response = res.json(data);
}
})
}
});
return response;
},
Function continues to executes other part of the function code after a return.
I am also getting "Cannot set headers after they are sent to the client" error on node. Im guessing, preventing the function to continue after sending a response will fix this as well?
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
There are two problems with the flow of your logic. First is that return only returns a function. It does not return the function that calls a function or the function that defines a function.
Basically, your code is:
Category.find({label: category}).exec(function(err, data) {
if(err) {
// ...
return;
} else if(/* ... */) {
// ...
newCategory.save(function(err, data){
if(err) {
// ...
return;
}
})
}
});
moreStuffDownHere();
// ...
Let's rewrite that to not use anonymous functions to make it clear what's really happening
function findCallback (err, data) {
if(err) {
// ...
return; // it's obvious that this returns form findCallback()
// and NOT yourFunction()
} else if(/* ... */) {
// ...
newCategory.save(saveCallback);
}
}
function saveCallback (err, data) {
if(err) {
// ...
return;
}
}
function yourFunction () {
Category.find({label: category}).exec(findCallback);
moreStuffDownHere();
}
So you can now see that you are not calling return anywhere in yourFunction().
The second problem is that Category.find().exec() is asynchronous. This means it returns immediately and let any code below it run before calling findCallback(). To solve the async issue just move moreStuffDownHere() inside findCallback().
Therefore, the simplest change to get your program flow working is to move moreStuffDownHere:
Category.find({label: category}).exec(function(err, data) {
if(err) {
res.status(400).send({message: 'Error finding category.'});
return;
} else if(data.length === 0) {
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
res.status(400).send({message: 'Error saving new category.'});
return;
}
// More stuff down here, that now will only execute if there are no errors
})
}
});
Improve program flow
One issue I have with the solution above is that now moreStuffDownHere is hardcoded inside the save callback. One way around it is to refactor the entire operation and make it your own internal API:
function addNewCategory (category, callback) {
// callback will be passed status depending on success or failure
Category.find({label: category}).exec(function(err, data) {
if(err) {
// ...
callback('FIND_FAILURE');
return;
} else if(/* ... */) {
// ...
newCategory.save(function(err, data){
if(err) {
// ...
callback('SAVE_FAILURE');
return;
}
callback('OK');
})
}
});
}
Now inside yourFunction() you can check the result of the entire operation and decide to return or continue:
function yourFunction() {
// ...
addNewCategory(category, function (status) {
switch (status) {
case 'FIND_FAILURE':
res.status(400).send({message: 'Error finding category.'});
return;
case 'SAVE_FAILURE':
res.status(400).send({message: 'Error saving new category.'});
return;
}
// More stuff down here ...
});
}
Improvement 2 - Promises
It's possible to make the program flow much easier to read by using Promises along with async/await. For that you need to wrap the operation in a promise. We can use the addNewCategory function we wrote above as an example:
function addNewCategory (category) {
// returns a Promise of true/false
return new Promise(function (resolve, reject) {
Category.find({label: category}).exec(function(err, data) {
if(err) {
// ...
resolve('FIND_FAILURE'); // you can also use reject if you want
// to use a try/catch flow
return;
} else if(/* ... */) {
// ...
newCategory.save(function(err, data){
if(err) {
// ...
resolve('SAVE_FAILURE');
return;
}
resolve('OK');
})
}
});
});
}
Now the code is slightly easier to follow because it allows you to keep moreStuffDownHere where you originally have it without moving it inside another function:
async function yourFunction() {
// ...
var status = await addNewCategory(category);
switch (status) {
case 'FIND_FAILURE':
res.status(400).send({message: 'Error finding category.'});
return;
case 'SAVE_FAILURE':
res.status(400).send({message: 'Error saving new category.'});
return;
}
// More stuff down here ...
}
Note: Express accepts functions marked as async as routes/middlewares. You just need to call res.send() or next() as usual
The error is as a result of your condition. Hence, both code blocks are run resulting in the response being sent twice. To fix this change your code to this below.
Category.find({label: category}).exec(function(err, data) {
if(err) {
res.status(400).send({message: 'Error finding category.'});
} else if(data.length>0) {
//there is no data with that label - Hence, create one
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
//if error
res.status(400).send({message: 'Error saving new category.'});
}else{
//if item saves
res.status(200).send({message: 'Item saved'});
}
})
}else{
//there is a data with that label availble - do something else
res.status(200).send(data)
}
});
The error you report happens when there are code paths that can send a response more than once. You get one and only one response per request. So, calling res.send() more than once is one way that you get that error.
Preventing this when you have a number of asynchronous operations requires a bit more work as you have to make sure that all your code is properly sequenced and all error paths are properly terminated (so further processing doesn't happen). In general, this code is a lot easier to write using promise-based interfaces for your asynchronous operations, but since you aren't using the promise interface on your database, I'll show how you can do it with your existing callback interface. In generally, it involves a lot of nesting inside of asynchronous callbacks and very careful if/else and return around conditionals and errors.
Your code is subject to this error because you are running Category.find() and Book.find() in parallel. You don't wait for the Category.find() code to finish before doing the book operations. If the category code causes an error, you will send that error response, but still continue with the book code which will then send its response. Instead, you need to make sure that if there's an error with the category stuff that you don't run the book code at all. For the plain callback interface on your database, that means nesting the book code inside a callback from the category code. To make this simpler to write, I put the category code into it's own function that has one completion callback that we can use to know when its all done.
Here's one way to do it:
// utility function to create category if needed, requires callback
// to communicate results
function createCategoryIfNeeded(category, fn) {
// checking if category already exist. if not, make a new category
Category.find({label: category}).exec(function(err, data) {
if(err) {
fn({message:'Error finding category.'});
} else if(data.length === 0) {
let newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if (err) {
fn({message:'Error saving new category.'});
} else {
// category created
fn(null, true);
}
})
} else {
// category already exists
fn(null, false);
}
});
}
// handle adding a new book request submission
addNewBook: function (req, res) {
var name = req.body.name.toLowerCase();
var url = req.body.url.toLowerCase();
var category = req.body.category.toLowerCase();
var tags = req.body.tags.toLowerCase();
createCategoryIfNeeded(category, function(err, created) {
if (err) {
res.status(400).send(err);
} else {
// checking if book name already exist
Book.find({name: name}).exec(function(err, data){
if(err) {
res.status(400).send({message:'Error validating Book existence'});
} else if(data.length > 0) {
res.status(200).send({message:'book name already exist'});
} else {
req.body.name = name;
req.body.url = url;
req.body.category = category;
req.body.tags = tags;
// make a new book document
var newBook = new Book(req.body);
newBook.save(function (err, data) {
if (err) {
res.status(400).send({message: 'Error saving new Book.'});
} else {
res.json(data);
}
});
}
});
}
});
},
The error meassage says that, res can be send once it has been send. So returning it along with the response.
Category.find({label: category}).exec(function(err, data) {
if(err) {
return res.status(400).send({message: 'Error finding category.'});
} else if(!data) {
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
return res.status(400).send({message: 'Error saving new category.'});
}
})
}
});

How to create data and render within the one get request

dashboardRouter.route('/:Teachersubjects/:Class/:Salary')
.get(function(req,res)
{
function handleErr(err,redir)
{
if(redir){
res.end("err");
}
else{
res.writeHead(200, {"content-type": "text/plain"});
res.end('Error occured');
}
}
Verify.verifySchoolUser(req,res,function(err,schooluserId){
if(err){
res.redirect('/schoolusers/login');
return;
}
else{
SchoolUser.findOne({_id:schooluserId}).exec(function(err,schooluser){
if(err)
{
console.log("NOUSER school")
handleErr(err);
return;
}
else
{
if(schooluser.count<4){
console.log(req.params.Class);
vacancies.create(req.params,function(err,vacancy){
if(err){
console.log(err.message);
console.log(err.name);
console.log("create error");
for (field in err.errors) {
console.log(err.errors[field].message);
}
handleErr(err);
return;
}
console.log(vacancy);
schooluser.vacancies.push(vacancy._id);
schooluser.save(function(err){
if(err){
console.log(JSON.stringify(err));
handleErr(err);
}
teacherforms.find({}, function (err, teacherforms) {
if (err) throw err;
else {
var teacherforms = teacherforms;
console.log(teacherforms);
var arr=[];
for(i=0; i<teacherforms.length; i++ ) {
arr.push(teacherforms[i]._id);
console.log(arr);
}
teacherusers.find({checking:{$in:arr}}, function (err, teacherusers){
if (err) throw err;
else{
var data =[];
for(i=0; i<teacherusers.length; i++ ) {
var kdata={};
var str = "";
kdata.firstname = teacherusers[i].firstname;
kdata.profilepic= teacherusers[i].profilepic;
kdata.experience= teacherforms[i].Teachingexperience;
var len= teacherforms[i].Qualifications.length;
console.log(teacherforms[i].Qualifications);
for(j=len-1;j>=0;j--){
if(j==0){
str = str + teacherforms[i].Qualifications[j];
}
else{
str=str + teacherforms[i].Qualifications[j] + ", ";
}
}
kdata.qualifications = str;
data.push(kdata);
}
data.push(vacancy.Class);
//data.push(vacancy.Qualification);
data.push(vacancy.Teachersubjects);
console.log(data);
res.render('../views/dashboard/dashboard_resumes.ejs',{
sdata:data
});
}
});
}
})
});
})
}
else {
res.send("Limit Exceeded");
}
}
});
}
});
});
I want to save a object firstly in MongoDB at get request and then render a page in ejs with a new object.But I am getting a error 500(Internal Server Error),so can we save a object and render the ejs page with new object at same end point?
P.S- The code below is the crucial part of the code and there is no syntax error and schooluser(object) is passed from the upper part of the code.
dashboardRouter.route('/:Teachersubjects/:Class/:Salary')
.get(function(req, res) {
vacancies.create(req.params, function(err, vacancy) {
if (err) {
console.log(err.message);
console.log(err.name);
console.log("create error");
for (field in err.errors) {
console.log(err.errors[field].message);
}
handleErr(err);
return;
}
schooluser.vacancies.push(vacancy._id);
schooluser.save(function(err) {
if (err) {
handleErr(err);
} else {
res.render('../views/dashboard/dashboard_resumes.ejs', {
sdata: data
});
}
});
});
});
There are multiple issues with your route handler. First, whenever an error occurs you run this through your handleErr() function, but you never do anything with res in these cases. This means that the request is never resolved and eventually times out on the client side.
When an error occurs, you should respond with an error code. For instance:
res.status(400).send('Something went wrong');
When you actually do send a response and render the template, you're applying a variable called data. However, this variable doesn't seem to be declared anywhere in your code. Maybe you left out that code, but if that variable is indeed undeclared, it throws a ReferenceError which is caught by the routing framework which in turn responds with a 500 Internal Server Error. So this is probably the source of your issue.
Any uncaught errors thrown by vacancies.create() or schooluser.save() would have the same effect.

Express Async - Can't set headers after they are sent, double callback?

I'm having an annoying as hell problem with 'Error: Can't set headers after they are sent.' in Express. This code originally worked using Restify rather than Express and I have been having this problem in one form or another since converting it.
The code makes 2 asynchronous requests to another API (edited out) and merges the results into a single JSON before storing it in MongoDB.
Any insight would be appreciated as I have tried all I can think of and have no idea why the same function would work in Restify and not Express with the appropriate changes.
Please don't comment on the Pyramid of Doom, I know it's not ideal, but that's not the focus here ;)
app.post('/rest/test', jsonParser, /*keycloak.protect(),*/ function (req, res, next) {
var requestObj = req.body;
try {
/* run async requests in parallel */
async.parallel([
function asyncReqV3(callback) {
request.post(reqUrl1, option_v3, function(err, response, body) {
if(err) {
console.log(err);
callback(true);
return;
} else {
callback(false, body);
}
});
},
/* v4 async request */
function asyncReqV4(callback) {
request.post(reqUrl2, option_v4, function(err, response, body) {
if(err) {
console.log(err);
callback(true);
return;
} else {
callback(false, body);
}
});
},
],
/* Collate results and send */
function collateResults(err, results) {
if(err) {
console.log(err);
return res.status(500).send("Server Error");
} else {
/* Merging results */
var hash = new Map();
results[0].body.krResult.forEach(function(obj) {
hash.set(obj.word, obj);
});
var final = results[1].body.data.map(function(obj) {
return Object.assign(hash.get(obj.word) || {}, obj);
});
var final_results = final.slice(0, 250).map(function(obj) {
return {
thing1: obj.something,
thing2: obj.somethingElse
}
});
/* Store results in DB */
db.results.insert({
request: requestObj,
response: final_results
});
res.status(200).send(final_results);
}
});
}
catch(e) {
console.log(e);
res.status(500).send({});
}
return next();
});
At the end of your route callback, remove:
return next();
You don't need it here and this line will always be executed before the async.parallel is done (collateResults in your code). You're quite likely sending a response in the subsequent routes and then again after your two requests are done.

Sending express response with data from 2 different mongoose queries

I need to query 2 different collections and send it in the express response. I have a very vague idea of what is needed to do so. I tried to contact the query documents to an empty array and send that new array as the response. But I receive an empty array as a response.
This is my route.
site.route('/campus/development')
.get(function(req, res) {
var devPosts = [];
development.find().exec(function(err, docs) {
if (err) {
console.log('Error : ' + err);
} else {
if (docs != null) {
devPosts = devPosts.concat(docs);
console.log(docs);
} else {
console.log('No posts found');
}
}
});
jobs.find().exec(function(err, jobs) {
if (err) {
console.log('Error : ' + err);
} else {
if (jobs != null) {
devPosts = devPosts.concat(jobs);
console.log(jobs);
} else {
console.log('No jobs');
}
}
});
res.send(devPosts);
});
This is due to the async operation of the requests to the database. There are a variety of solutions to this but basically distill down to two types: callbacks or promises.
A callback solution might look like:
site.route('/campus/development')
.get(function(req, res) {
development.find().exec(function(err, devDocs) {
if (err) {
console.log('Error : ' + err);
} else {
if (devDocs != null) {
console.log(devDocs);
jobs.find().exec(function(err, jobs) {
if (err) {
console.log('Error : ' + err);
} else {
if (jobs != null) {
console.log(jobs);
res.send([devDocs, jobs]);
} else {
console.log('No jobs');
}
}
});
} else {
console.log('No posts found');
}
}
});
});
But this introduces a couple of interesting issues: one is the phenomenon known as callback hell and the other is that you should be responding with the errors which means you would need to have a response call for each error (albeit this is a very simplistic approach to it).
As mentioned earlier there is another type of solution which involves using promises. There are a bunch of libraries that you can use and actually Mongoose returns a promise from the exec method. However if you are on Node 0.12.x you can also use the native Promise (it was introduced in 0.11 but you should be using 0.12.x over 0.11.x). A benefit to using the native promise over the one returned from Mongoose is that you can execute these requests in parallel since they don't depend on each other.
site.route('/campus/development')
.get(function(req, res) {
Promise.all([
development.find().exec(), // returns a promise
jobs.find().exec() // returns a promise
]).then(function(results) {
// results is [devDocs, jobs]
console.log(results);
res.send(results);
}).catch(function(err) {
res.send(err);
});
});

Resources