Mongoose document created. But change is not reflected immediately in the collection - node.js

favoriteRouter
.route('/:dishId')
.post(cors.corsWithOptions, authenticate.verifyUser, (req, res, next) => {
Favorites.findOne({ user: req.user._id })
.then(
(favoriteList) => {
//***SECTION A START//***
if (favoriteList == null) {
Favorites.create({
user: req.user._id,
})
.then(
(favoriteList) => {
console.log('promise resolved');
},
(err) => {
console.log('promise error');
next(err);
}
)
.catch((err) => next(err));
}
//***SECTION A END//***
Favorites.findOne({ user: req.user._id })
.then((favoriteList) => {
//***SECTION B START//***
if (favoriteList != null) {
favoriteList.dishes.push(req.params.dishId);
favoriteList
.save()
.then(
(favoriteList_c) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favoriteList_c);
},
(err) => {
next(err);
}
)
.catch((err) => {
next(err);
});
} else {
err = new Error(
'Something wrong with favorite list document of user ' +
req.user._id
);
err.status = 404;
return next(err);
}
//***SECTION B END//***
})
.catch((err) => next(err));
},
(err) => next(err)
)
.catch((err) => next(err));
});
If a user post on /:dishId and the favourite document is not there for that user then a new document is created
in SECTION A (marked in code). The document is created fine as it prints promise resolved. But in SECTION B the else part is executed that means the newly created document is not found. But if the user tries again means in next go it can find that document and it gets updated in SECTION B IF block. Is there something I am missing. I am a beginner in nodejs, Please help!

You have to first understand the way NodeJs asynchronous code work. The default behavior of NodeJs is that whenever it finds an I/O operation, it delegates that opeation to either OS or worker thread based on OS capability in handling it and moves to the next line of code.
According to your code, the first Favorite.findOne() is called and while it is being executed by the engine, control jumps over to the next line of code, which is the second Favorite.findOne() and it tries to find the document. But At this point the document has not been created yet, So that's the reason when you run for the first time, it doesn't find the record, but for the second time onwards the document has been created using the Favorite.create() inside the first findOne's then().
So you need to re-factor your code by putting the second findOne() inside the firstOne. Well, you know what, you don't need to write the Favorite.findOne() two times. One findOne() is sufficient to accomplish your requirement.
Mongoose leverages promises, it means we can use async/await in the controller method.
favoriteRouter
.route('/:dishId')
.post(cors.corsWithOptions, authenticate.verifyUser, async (req, res) => {
try {
// find the FavoriteList
let favoriteList = await Favorites.find({
user: req.user._id
});
// using a library called lodash for simplicity
// you have to install it using npm i lodash and
// require at the top using let _ = require('lodash')
//If not found, create one and assign it to favorite list
if (_.isEmpty(favoriteList)) {
favoriteList = await Favorites.create({
user: req.user._id
});
}
// at this point, you must have a favoriteList either a found one or brand new
favoriteList.dishes.push(req.params.dishId)
let favoriteList_c = await favoriteList.save();
return res.json(favoriteList_c)
} catch (err) {
//handle error
res.status(501).send({message: 'unable to perform operation'});
}
});
I have added async to the controller callback function here and reomved the next parameter, as we don't need it. For reference visit this.
NOTE: the code I've wrtitten may not work if you simply copy/paste it in your program but the approach is fairly straight forward and you may need to do some tweakings based on Mongoose API documentation especailly for save() and create().
I am sure they will return the object after creating it.
Good Luck!!

There is no problem in your code but when the findOne of section A is getting executed at the same time section B code gets executed.
Using promise.then creates a complex code. Use async/await instead.
The following is just a small snippet from your code to use async/await
let favoriteList = await Favorites.findOne({ user: req.user._id });
if (favoriteList == null) {
await Favorites.create({
user: req.user._id,
})
}
favoriteList = await Favorites.findOne({ user: req.user._id })
if (favoriteList != null) {
favoriteList.dishes.push(req.params.dishId);
await favoriteList.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favoriteList_c);
} else {
err = new Error(
'Something wrong with favorite list document of user ' +
req.user._id
);
}

Related

MongoDB How to catch errors in deleteMany

I have looked at MongoDB documentation for deleteMany() but it seems the only error it throws is WriteConcernError.
I am using Insomnia to make my requests.
Here is my request:
DELETE HTTP://localhost:5000/api/users/delete/usernames?usernames=["a","b","c"]
As you can see I have an array in query string
so I pass that to my function
# user.controller.js
function _deleteByUsernames(req, res, next) {
userService.deleteByUsernames(JSON.parse(req.query.usernames))
.then(() => res.status(200).json({"message": "User(s) successfully deleted!"}))
.catch(err => next(err));
}
# user.service.js
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
await User.deleteMany({username: {$in: usernames}});
} catch (err) {
throw err;
}
}
I know there no documents with usernames a, b and c
but deleteMany() doesn't return any error something like "Coulnd't find given parameter" etc.
because I don't want to response with "User(s) successfully deleted".
How can I catch that error if there is one.
Or How should I handle that?
You may change your functions to below,
# user.controller.js:
put async/await in function, and add code in try/catch block, and pass res as param in service function deleteByUsernames,
async function _deleteByUsernames(req, res, next) {
try {
await userService.deleteByUsernames(res, JSON.parse(req.query.usernames));
} catch (err) {
next(err);
}
}
# user.service.js:
deleteMany(), This function calls the MongoDB driver's Collection#deleteMany() function. The returned promise resolves to an object that contains 3 properties:
ok: 1 if no errors occurred
deletedCount: the number of documents deleted
n: the number of documents deleted. Equal to deletedCount.
async function _deleteByUsernames(res, usernames) {
let response = await User.deleteMany({ username: { $in: usernames } });
// ERROR
if (response.ok != 1) {
res.status(400).json({ "message": "User(s) not deleted, Something want wrong!" });
}
// SUCCESS
else {
res.status(200).json({
"message": `${response.deletedCount} User(s) successfully deleted out of ${response.n}"
});
}
}
Code is not tested, you can workaround and see what happens!
I think there is no error for no found parameters.
I don't know this is better than nothing for now.
I am not going to mark this as answered because I don't think this is the answer
async function _deleteByUsernames(usernames) {
return await User.deleteMany({username: {$in: usernames}})
.then(result => {
console.log(result);
return (result.deletedCount === 0 ?
"None of the selected user(s) deleted!":
(result.deletedCount !== usernames.length ?
`${result.deletedCount} out of ${usernames.length} selected user(s) successfully deleted!`:
"All selected user(s) successfully deleted!"))
})
.catch(err => {
return `Delete failed with error: ${err}`;
})
}
You can save your delete result in a variable and check for the error
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
let userDeleteResult = await User.deleteMany({username: {$in: usernames}});
if(!userDeleteResult ){
res.json({status: false, error: 'Some error'}) // or pass the error object here
}
} catch (err) {
throw err;
}
}

Getting Error: Can't set headers after they are sent

I am trying to implement search functionality in Node, mongoose.
There is two parameter I like to search upon, either by name or artist. If any of the two matches with the current database it should return value(making it restful)
However, it is sending response Error: Can't set headers after they are sent.
and Unhandled promise rejections are deprecated and even the response which i am getting is empty
I am trying to execute two queries in it, which i think might be the problem. How should i write it, or what is a correct way to write these type of functionality
Here is my current code
app.get('/search/:textValue', controller.findData)
and the findData
exports.findData = (req, res)=>{
const searchParam = req.params.textValue;
let storeResult = []
if(searchParam==null|| searchParam == undefined || searchParam==""){
return res.status(500).json("Send a valid input")
}
else{
Song.find({artists: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[0].push(data)
}).catch((err)=>{
return res.send(err)
})
Song.find({name: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[1].push(data)
}).catch((err)=>{
return res.send(err)
})
return res.send(storeResult)
}
}
They are working for single queries perfectly fine, what changes should be made over here ?
The way you have it you're using res.send(storeResult) before you fill in storeResult. How so? You fill it in with your .then() callbacks, which haven't yet been invoked.
Try chaining your then callbacks.
Song.find({artists: new RegExp(searchParam, "i")}).lean()
.then((data)=>{
storeResult.push(data);
})
.then(() => {
Song.find({name: new RegExp(searchParam, "i")}).lean()
.then((data)=>{
storeResult.push(data)
})
.then(() => {
console.log(storeResult)
res.send(storeResult)
})
})
.catch((err)=>{
console.log("Here is error")
console.log(err)
res.send(err)
})
}
Hint. Step-into in your debugger is useful for troubleshooting this kind of code.
Try this:
exports.findData = (req, res)=>{
let count=0;
const searchParam = req.params.textValue;
let storeResult = []
if(searchParam==null|| searchParam == undefined || searchParam==""){
return res.status(500).json("Send a valid input")
}
else{
Song.find({artists: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[0].push(data)
}).catch((err)=>{
count++;
return res.send(err)
})
if(count == 0) {
Song.find({name: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[1].push(data)
}).catch((err)=>{
count++;
return res.send(err)
})
}
if(count == 0) {
return res.send(storeResult)
}
}
}
Problem
You're starting with empty array let storeResult = []
Then you access its first element (which does not exist) storeResult[0].push(data)
This will trigger your catch callback. And then do a res.send(err)
Even if you called return it will still continue in (req, res) => {} . This is because the return is only for the (err) => { // } callback
Same thing with storeResult[1].push(data)
Finally you call return res.send(storeResult) which effectively finishes your (req, res) => {} callback and return another response to the client
Solution:
When you push to your storeResult array, omit the index. Like this
storeResult.push(data)
Note
Even when pushing correctly, an error might happen while accessing the database. This is why you also need to chain your callbacks like O. Jones answer says

Async/await in Express with multiple MongoDB queries

I have a fairly straightforward CRUD app which renders the results of two queries onto one page. The problem that arose once I got this to "work" was that the page required a refresh in order to display the results. On first load, no results were displayed.
I came to figure out that this is a problem/symptom of Node's asynchronous nature. I've been trying to approach this problem by using async/await, and from hours of messing with things, I feel like I'm quite close to the solution, but it's just not working out - I still need a manual refresh to display/render the results on the .ejs page.
The code:
var entries = [];
var frontPageGoals = [];
app.get('/entries', async (req,res) => {
if (req.session.password) {
const entriesColl = await
db.collection('entries')
.find()
.sort({date: -1})
.toArray((err, result) => {
if (err) { console.log(err) }
else {
for (i=0; i<result.length; i++) {
entries[i] = result[i];
}
}
});
const goalsColl = await
db.collection('goals')
.find()
.toArray((err, result) => {
if (err) {console.log(err)}
else {
for (i=0; i<result.length; i++) {
frontPageGoals[i] = result[i];
}
}
});
res.render('index.ejs', {entries: entries, frontPageGoals: frontPageGoals});
}
else {
res.redirect('/');
}
});
Now, I can conceive of a few problems here, but honestly I'm just at my wits end trying to figure this out. For example, I'm sure it's problematic that the empty lists which will contain the results to be passed when the page renders are outside the actual async function. But after trying to move them a dozen different places within the async area... still no dice.
Any help would be hugely appreciated! This is basically the last big "thing" I need done for this app.
I'm not 100% sure about your database driver, but assuming that the toArray() returns a promise (which it does in the default mongodb driver), the await will actually return the value you expect in your callback, result in your case, or in case there was an error, which you expected it as err in your callback, it will be thrown, thus forcing you to use try-catch blocks, in your case, you would just use console.log(err) in the catch block, since you aren't doing any handling
Here's your code after updating :
app.get("/entries", async (req, res) => {
if (req.session.password) {
try {
const entries = await db
.collection("entries")
.find()
.sort({ date: -1 })
.toArray();
const frontPageGoals = await db
.collection("goals")
.find()
.toArray();
res.render("index.ejs", {
entries: entries,
frontPageGoals: frontPageGoals
});
} catch (err) {
console.log(err);
}
} else {
res.redirect("/");
}
});
EDIT
However, if you don't know about promises -which async/await are basically promises-, and wanna just do it using callbacks -not advised-, you would have to just send your response in the callback, and nest the 2nd query in the first query's callback, here is the code,, with some comments to hopefully help you out:
app.get("/entries", (req, res) => {
if (req.session.password) {
// First query
db.collection("entries")
.find()
.sort({ date: -1 })
.toArray((err, entryResult) => {
if (err) {
console.log(err);
} else {
// In the callback of the first query, so it will
// execute 2nd query, only when the first one is done
db.collection("goals")
.find()
.toArray((err, frontPageResult) => {
if (err) {
console.log(err);
} else {
// In the callback of the 2nd query, send the response
// here since both data are at hand
res.render("index.ejs", {
entries: entryResult,
frontPageGoals: frontPageResult
});
}
});
}
});
} else {
res.redirect("/");
}
});
I have removed the async keyword since you no longer need it
I renamed the callback arguments, instead of just result, because both callbacks would have the same argument name, and you would have had to store it in a temp variable

ExpressJS - Refactoring nested callbacks and handling errors in app.get functions?

I am quite new to Node.js and already frustrated due to nested callbacks which make it very hard to read the code and troubleshoot for typos.
As you can see below, I have 2 associated models (Blog and Comment) and app.get method which I create Comment for a Blog post.
Model Structure:
Blog
..title (string)
..blog (string)
..comments (Referenced Comment Model)
....comment (string)
Comment
..comment(string)
Currently app.get method has 3 nested call back functions, possible errors are only console.logged yet (for a better user experience if I start to write more codes for errors function becomes real mess).
app.post('/blog/:id/comment',function(req,res){
Comment.create(req.body.comment, function(err, newComment){
if (err) {
console.log(err);
} else {
Blog.findById(req.params.id, function(err, foundBlog){
if (err){
console.log(err);
} else {
foundBlog.comments.push(newComment);
foundBlog.save(function(err, data){
if(err){
console.log(err);
} else {
res.redirect('/blog/'+req.params.id);
}
});
}
});
}
});
});
Here I would like to ask your suggestions to simplify below function and how to better handling errors.
As others have commented, promises is the way to go and async/await is generally the most elegant approach to writing promises. As an example, your code could be condensed to the below. You should read up on promises as they are an important concept for node development.
app.post('/blog/:id/comment', async function(req,res){
try{
const newComment = await Comment.create(req.body.comment);
const foundBlog = await Blog.findById(req.params.id);
foundBlog.comments.push(newComment);
await foundBlog.save();
res.redirect('/blog/'+req.params.id);
}
catch(err){
console.log(err);
}
});
Looks like you are using Mongoose, which supports promises, so you could do something like this:
app.post('/blog/:id/comment',(req,res) {
Comment.create(req.body.comment)
.then(newComment => {
return Blog.findById(req.params.id))
.then(foundBlog => {
foundBlog.comments.push(newComment)
return foundBlog.save()
})
})
.then(() => res.redirect('/blog/' + req.params.id))
.catch(err => console.log(err))
})
You could also use async-await:
app.post('/blog/:id/comment', async (req, res) {
try {
const newComment = await Comment.create(req.body.comment)
const foundBlog = await Blog.findById(req.params.id)
foundBlog.comments.push(newComment)
await foundBlog.save()
res.redirect('/blog/' + req.params.id)
}
catch(err) {
console.log(err)
}
})

Unable to use .then in express framework

I'm new to Express framework and learning, I'm having a problem using .then. The problem is I have 2 functions and I want the first one to complete before the second to start executing. I'm exporting the modules.
var ubm = require('./userBasic');
There are 2 functions setUserData and showUserId, the showUserId must execute only after setUserData has performed its operation.
var userId = ubm.setUserData(userName,userEmail,userDOB,moment);
userId.then(ubm.showUserId(userId));
Below are the 2 functions:
module.exports = {
setUserData: function (userName,userEmail,userDOB,moment){
//Performing database activities
return userId;
}
showUserId: function (userId){
console.log(userId);
}
}
When i run it says TypeError: Cannot read property 'then' of undefined.
Like I said I'm very new and learning and was unable to figure out the solution. I did some google search and got a brief about promise, but I don't know how to implement here.
Try using promises
module.exports = {
setUserData: function(userName, userEmail, userDOB, moment) {
return new Promise(function(resolve, reject) {
//db stuff
reject(error);
resolve(userId);
});
},
showUserId: function(userId) {
console.log(userId);
};
};
So in your execution you would write
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
showUserId(data);
})
.catch((err) => {
console.log(err);
});
A couple of things to note is that in this instance you could just log data without the need for another function like
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
Whatever value you pass into resolve() will be returned as well as you pass errors into reject().

Resources