How to exit after response in nodejs with express? - node.js

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.'});
}
})
}
});

Related

Refactoring RESTful API into smaller functions

Background
I have a NodeJS app that is meant to be used as a RESTful API. It is connected with a MongoDB database in the backend using Mongoose. The app is built upon the idea of nested documents. It stores wikis, sections and notes with the following schema:
const noteSchema = new mongoose.Schema({ title: String, content: String });
const sectionSchema = new mongoose.Schema({ title: String, notes: [noteSchema] });
const wikiSchema = new mongoose.Schema({ title: String, sections: [sectionSchema] });
All of which are accessed via a single model of the wiki:
const wikiModel = mongoose.model("Wiki", wikiSchema);
A user can do GET, POST, PUT, DELETE requests on each of the endpoints to manipulate the data inside. If someone wants to ping the Notes endpoint (the furthest down in the hierarchy), it must first check the wiki and then the section endpoint, to ensure that each of them exists.
Here's an example:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
wikiModel.findOne({ title: req.params.wikiTitle }, function(err, wiki) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (wiki) {
const sectionTitle = req.params.sectionTitle;
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (section) {
const noteTitle = req.params.noteTitle;
wikiModel.findOne({ 'sections.notes.title': noteTitle }, function(err, n) {
if (err) {
res.send('\nAn unkown error has occured');
console.error(err);
} else if (n) {
const section = n.sections.find((s) => { return s.title === sectionTitle; });
const note = section.notes.find((n) => { return n.title === noteTitle; });
if (note.content) {
res.send('\n' + note.title + '\n\n' + note.content);
} else {
res.send('\n' + note.title + '\n\n[ No content to show ]');
}
} else {
res.send('\nNo such note exists');
}
});
} else {
res.send('\nNo such section exists');
}
});
} else {
res.send('\nNo such wiki exists');
}
});
});
This is a very lengthy method and the first two queries are actually frequently throughout the app. I also understand a MongoDB query is an asynchronous operation and thus, why I put each consequent MongoDB query within it's parent (the one I wish to finish before that one begins).
Question
Is there a way to split each MongoDB query into its own method or introduce promises in a way that would shorten the code? I would rather prefer advice that ultimately causes the splitting of my code into individual methods as what you see above is one of many endpoints which all use the same queries.
So in the end result I would like to have something close to the likes of:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
if (getWiki(req.params.wikiTitle)) {
// Continue with second query
if (getSection(req.params.sectionTitle)) {
// Continue with third query...
}
}
});
function getWiki(wikiTitle) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return wiki
} else {
res.send('No wiki found');
return null;
}
});
}
function getSection(sectionTitle) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
res.send('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return section
} else {
res.send('No section found');
return null;
}
});
}
I am hoping this will significantly cut the length of code and also utilise re-usability of code. Any advice on how I could come close to achieving something like this is welcome.
You can definitely use callbacks in the same way as the ones call your model. For example:
app.get('/:wikiTitle/:sectionTitle/:noteTitle', function(req, res) {
getWiki(req.params.wikiTitle, function (err, title) {
if (err) {
return res.send(err);
}
getSection(req.params.sectionTitle, function (err, section) {
if (err) {
return res.send(err);
}
// Todo: use title and section, etc...
});
});
});
function getWiki(wikiTitle, cb) {
wikiModel.findOne({ title: wikiTitle }, function(err, wiki) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (wiki) {
// Send OK result to continue to next query
return cb(null, wiki);
} else {
return cb('No wiki found');
}
});
}
function getSection(sectionTitle, cb) {
wikiModel.findOne({ 'sections.title': sectionTitle }, function(err, section) {
if (err) {
console.error(err);
return cb('An unknown error occured.');
} else if (section) {
// Send OK result to continue to next query
return cb(null, section);
} else {
return cb('No section found');
}
});
}
This is a standard way of using async functions in node. By convention, the first parameter is always an error parameter.
If you want your code to be cleaner, you can try to use guard clauses / early outs to exit error cases early. This will cut down on your need for if / else conditional statements.
You can also look into libraries like async for cleaner chaining of asynchronous calls.
When you are comfortable, you can also look into using promises and the 'async' javascript keyword (different from the async library above, confusing, I know) which will also allow you to cut down on the lines of code you have to write to get nice async code.
You should use async functions (Promises) like
app.get('somePath', async (req, res, next) => {
try {
const doc = await model.find({ someField: 'some value' }).exec(); // exec returns promise
res.send({ document: doc });
} catch (error) {
// here you can handle all errors or/and call next for the error middleware
next(error);
}
});

Node Js call back / promise/ return

I am kind of new to this node js and went through many explanations, tried many solutions but still cant get my head wrapped around the function call backs.
//app.js file
var dashboardfunc = require('./models/admindashboard');
app.get("/dashboard/:id?", function(req, res) {
console.log("we are here in dashboard")
var data = {id: req.params.id};
console.log(data)
dashboardfunc.productlist().then(function(results){
console.log("i am here now ....")
console.log(results)
}).catch(function(err){
if(err){
console.log(err)
}
})
});
//admindashboard.js file
//I tried many other alterations like using call back etc.
// i want the damn results to be back to the app.js and use that
//
function productlist(data) {
return new Promise(function(resolve, reject) {
var param = [data.id];
var sql = 'select * from product where seller_id=?';
console.log(param)
pool.query(sql, param, function(err, results) {
if (err) {
console.log(err)
}
else {
if (results === undefined) {
reject(new Error("Error rows is undefined"));
}
else {
console.log("we got here in productlist")
console.log(results)
return results;
}
}
})
})
}
module.exports = productlist;
<--Result -->
Rb-v2 started !!!
we are here in dashboard
{ id: '23' }
TypeError: dashboardfunc.productlist is not a function
Question is why it is so hard to get the results back , and why it needs to be so complicated to call a function , get the return data. Along with that whats the deal with callback v/s promise ( yeah I read almost all post in it still my naive brain cant process it)
Try these small fixes for the start:
admindashboard.js exports the only function, but app.js tries to use it as a property of an object. You need either this type of export:
module.exports = { productlist };
or this using:
dashboardfunc().then
The argument in the imported function call is missing. Try dashboardfunc(data).then instead of mentioned dashboardfunc.productlist().then.
resolve callback is not used in productlist() function. Use it to return the data from the promise: resolve(results); instead of return results;.
Be consistent in error handling. Use:
if (err) {
reject(err)
}
else {
if (results === undefined) {
reject(new Error("Error rows is undefined"));
}
instead of:
if (err) {
console.log(err)
}
else {
if (results === undefined) {
reject(new Error("Error rows is undefined"));
}

Error: can't set headers after they are sent

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.

How to get return value from the function while initialising the object in node js

I am mew to node js, I have something like this,
get_contacts(data, function(contacts) {
if (contacts.length) {
var count = contacts.length;
for (var i = 0; i < count; i++) {
result = {
id: contacts[i].id,
name: contacts[i].name,
sent1: get_sent(data.userId, contacts[i].id, function(resp) {
result.sent = resp.count;
}),
}
result1[i] = result;
}
output = {
contacts: result1,
}
} else {
output = {
error: "No Contacts.",
}
}
res.writeHead(200, {'content-type': 'text/html'});
res.end(JSON.stringify(output));
});
get_contacts is a callback function which will return contact list.result1 & result are objects. Now value for sent should come from a function get_sent, and get sent is like this
function get_sent(userId, contactId, callback) {
pool.getConnection(function(err, connection) {
connection.query("my query here", function(err, rows) {
connection.release();
if (!err) {
callback(rows);
} else {
console.log(err)
}
});
});
}
But im not getting any value since nodejs. since nodejs is async it is not waiting for the function to return value. I know, im doing it in wrong way. Please help
You need to use a callback. In simple words is a function that you'll execute after something happens. You should read more about that. You should get a book about javascript but you can start reading here for example.
About your case, you could solve it like this
//Asumming that you object `result` is global.
result = {
id: contacts[i].id,
name: contacts[i].name,
sent: -1 //Some default value
}
//Just to put the code into a function, you have to put it where you need
function constructObject (){
get_sent(uId, cId, function(err, total){
if(err){
console.log("Something was wrong.", err);
}
result.sent = total;
//Here you have your object completed
console.log(result);
});
}
//You need to use a callback
function get_sent(uId, cId, callback) {
pool.getConnection(function(err, connection) {
//Note that I add an alias here
connection.query("SELECT count(*) as total FROM table_name", function(err, rows) {
connection.release();
if (!err) {
//I am returning the result of the query and a null error
callback(err, rows[0].total);
} else {
console.log(err);
//I am returning an error
callback(err);
}
});
});
}
//For example you could call this function here
constructObject();
And it depends of what are you doing exactly but Maybe you need a callback on your constructObject too.

How to create callback post saving an array of objects?

Assuming I have the following in a function:
exports.addnames = function(req, res) {
var names = ["Kelley", "Amy", "Mark"];
for(var i = 0; i < names.length; i++) {
(function (name_now) {
Person.findOne({ name: name_now},
function(err, doc) {
if(!err && !doc) {
var personDoc = new PersonDoc();
personDoc.name = name_now;
console.log(personDoc.name);
personDoc.save(function(err) {});
} else if(!err) {
console.log("Person is in the system");
} else {
console.log("ERROR: " + err);
}
}
);
)(names[i]);
}
My issue is after I save the names, I want to return the results:
Person.find({}, function(err, doc) {
res.json(200, doc);
})
Though I have a callback for names, it appears that the last block of code (Persons.find({})) gets executed before the calls to save all the names is complete... thusly when the user goes to the url in the browser, "doc" is empty... Is there some way I can ensure that the Persons.find({}) is called after the for loop completes?
The easiest way to do things like this is to use an async library like the aptly named async which can be found at https://github.com/caolan/async.
If you have a list of names that you want to save and then return when complete, it would look like:
// save each of the names asynchronously
async.forEach(names, function(name, done) {
Person.findOne({name: name},
function(err, doc) {
// return immediately if there was an error
if(err) return done(err);
// save the person if it doesn't already exist
if(!doc) {
var personDoc = new PersonDoc();
personDoc.name = name;
console.log(personDoc.name);
// the async call is complete after the save completes
return personDoc.save(done);
}
// or if the name is already there, just return successfully
console.log("Person is in the system");
done();
}
);
},
// this function is called after all of the names have been saved
// or as soon as an error occurs
function(err) {
if(err) return console.log('ERROR: ' + err);
Person.find({}, function(err, doc) {
res.json(200, doc);
})
});

Resources