Delay in return value - nodejs & mongoose - node.js

I'm fairly new to nodejs and I'm doing a full stack developer challenge from devchallenges.io (Shoppingify). Below, I'm trying to increase the quantity value based on whether the user clicks to increase or decrease the item quantity. However, there's a slight delay between the return value from the request and the actual value in the database. It seems that the value updates immediately which is great however, the return value in the request is the previous value rather than being the current quantity value in the database.
mongoDB Database
// #route PUT api/list/item/quantity/:id
// #desc update item quantity
// #access Private
router.put('/item/quantity/:id', auth, async (req, res) => {
const { action } = req.body;
try {
let list = await List.findOne({ user: req.user.id });
// find current quantity
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
// increase quantity
if (action === 'increase') {
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity + 1 } },
{ new: true }
);
} else {
// decrease quantity
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
}
res.json(item.quantity);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});

You are defining item in here:
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
At this point list is the "old" version of the object, you want to be doing the same after the update when the list object is updated and only then to return it.
// this is the original "list" item
let item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
...
update list
...
// now "list" is updated
item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
I will just add two additional tips to improve performance, they are mutually exclusive so you'll have to choose one of the two.
in the update query add the list._id to it, If I were to guess the collection does not have an index on the items field ( and if it does it's a bad idea usually ). this means when you updated just using the item._id field it takes longer for mongo to find the object. it's quick change to both updates:
list = await List.findOneAndUpdate(
{ _id: list._id, 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
(my preferred option) do it in a single call, using the update arrayFilters option, like so:
const list = await List.findOneAndUpdate(
{
user: req.user.id,
},
{
$inc: {
'items.$[elem].quantity': action === 'increase' ? 1 : -1,
},
},
{
arrayFilters: [
{
'elem._id': new ObjectId(req.params.id),
},
],
new: true,
});
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
Mongo Playground
In my opinion now your route looks much better, you're also cutting down from 2 db calls to 1.

Related

Getting an {"message":"Invalid update pipeline operator: \"_id\""} error

I am trying to update two rows in my players table based on the id. I am trying to use the updateMany method where id can be found in an array of id's but I am getting the {"message":"Invalid update pipeline operator: \"_id\""} error. I checked the array to make sure it is valid id's. Here is my code
const winningTeam = asyncHandler(async (req, res) => {
req.body.forEach((element) => {
element.wins += 1;
element.lastPlayed = Date.now();
element.percentage = (element.wins / (element.wins + element.wins)) * 1000;
});
let usersId = [];
usersId.push(req.body[0]._id);
if (req.body.length === 2) {
usersId.push(req.body[1]._id);
}
const player = await Player.updateMany({ _id: { $in: usersId } }, req.body);
if (player) {
res.status(200).json(player);
} else {
res.status(400);
throw new Error("Invalid Data");
}
});
You should use $set property for the update parameter. I'm not sure about the structure of your req.body but it should be something like this:
Player.updateMany({ _id: { $in: usersId } }, {$set: req.body});
instead of this:
Player.updateMany({ _id: { $in: usersId } }, req.body);
Take a look at docs for updateMany

Unable to check user id into mongoose database array and giving error?

I have a model photos which has likes field and likedBy fields. Storing user id into liked by field and increase the like number based on user. I am not able to check if user already liked the image he should not like it again.
My route is :
exports.postLikes = (req, res, next) => {
const action = req.body.action;
const counter = action === 'Like' ? 1 : -1;
if (action == 'Like') {
PhotoEntries.findById({
_id: req.params.id
}, (err, photo) => {
const alreadyDownvoted = photo.likedBy.includes(req.user.id);
if (alreadyDownvoted) {
let _downvoted = photo.likedBy.filter(id => id !== req.user.id);
photo.likedBy = _downvoted;
PhotoEntries.update({
_id: req.params.id
}, {
$inc: {
likes_count: +1
},
$push: {
likedBy: req.user._id
},
LikeorUnlike: true
}, (err, photo) => {
})
}
})
} else {
PhotoEntries.findById({
_id: req.params.id
}, (err, photo) => {
const alreadyUpvoted = photo.likedBy.includes(req.user.id);
if (alreadyUpvoted) {
PhotoEntries.update({
_id: req.params.id
}, {
$inc: {
likes_count: -1
},
$pull: {
likedBy: req.user._id
},
LikeorUnlike: false
}, (err, photo) => {
})
}
})
}
res.send('');
}
I am able to store user into likedBy field and as well able to increase +1 if user liked it but Unable to verify whether user already liked or not and also not able to store the like button state.
If user like the button it is come to unlike but when I refresh it become like and but like count will saving in data base.
I am using AXIOS :
var updatePostStats = {
Like: function(postId) {
document.querySelector('#likes-count-' + postId).textContent++;
},
Unlike: function(postId) {
document.querySelector('#likes-count-' + postId).textContent--;
}
};
var toggleButtonText = {
Like: function(button) {
button.textContent = "Unlike";
},
Unlike: function(button) {
button.textContent = "Like";
}
};
var actOnPost = function(event) {
var postId = event.target.dataset.postId;
var action = event.target.textContent.trim();
toggleButtonText[action](event.target);
updatePostStats[action](postId);
axios.post('/' + postId + '/like', {
action: action
});
};
I have two issues:
One is not able to verify user id in the array of 'likedby' whether is it exist or not and other one after like button become unlike, if i refresh the page it again comes to like.
I am able to update like to unlike basis on user id stored in likedby. How can achieve that?

Updating a nested objects in Mongoose

I have the following express route:
const updateSnapshot = async (req, res) => {
const accountId = req.body.account_id;
if (!accountId) {
return fail(res, 'account id is missing', 400);
}
try {
const account = await Account
.findOne({ _id: accountId})
.populate({
path: 'snapshot',
model: 'Snapshot'
});
// I want to update these fields in snapshot
const snapshot = {
friends_count: data.friends_count,
updated_date: new Date()
};
account.snapshot.friends_count = snapshot.friends_count;
account.snapshot.updated_date = snapshot.updated_date;
await account.save();
return success(res, snapshot);
} catch(error) {
fail(res, error.message, 500);
}
};
I want to update the nested object snapshot (just the fields friends_count and update_date) however when I check the database it seems to have not work. What am I doing wrong here?
const updateSnapshot = (req, res) => {
const accountId = req.body.account_id;
if (!accountId) {
return fail(res, 'account id is missing', 400);
};
Account
.findOneAndUpdate({ _id: accountId }, {
$set: {
snapshot: {
friends_count: data.friends_count,
updated_date: new Date()
}
}
}, {
new: true
})
.then(account => {
if(!account) {
return fail(res, "Account not found", 404);
} else {
return success(res, account);
};
})
.catch(err => {
return fail(res, error.message, 500);
});
};
Here we're using the findOneAndUpdate method and promises to find and update the document in one operation.
findOneAndUpdate takes the query criteria as the first parameter, the second parameter updates values specified in the object (we're using the $set operator to update specific values of the snapshot object), and the three parameter is an object that defines the operation's options (new is set to true to return the newly updated object).
Note: $set will replace the entire snapshot object so if there are other properties inside the snapshot object, they will need to be included inside the $set object.

How to use variable in promise? NodeJS

I'm writing a shop-ecomerce website. I want to add product to my cart, so there are two circumstances
1. The product with that id is in the cart => add more quantity (SOLVED)
2. The product is not exist => create new one
So how to check if a product (with specific id) exist in cart?
I use another variable (let exist) to check but it seems doesn't work (due to promise, I think)
// Add product to cart
router.post('/add', checkToken, (req, res) => {
let _idProduct = req.body._idProduct;
let quantity = req.body.quantity;
let exist = false;
Cart
.findOne({ user: req.decoded.userId })
.exec()
.then(cart => {
cart.items.map(item => {
// Product exist => add more quantity
if (item.product == _idProduct) {
item.quantity += quantity;
}
})
// How to check if no product with that id in Cart ??
cart.save(err => console.log(err));
res.json({
cart: cart
})
})
.catch(err => { console.log(err)});
})
Cart model
var Cart = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [
{
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
quantity: { type: Number }
}
],
})
You can use a boolean/flag here to see if the item is found.
cart.items.map(item => {
let found = false; // boolean to see if we found the item
// Product exist => add more quantity
if (item.product == _idProduct) {
item.quantity += quantity;
found = true; // if we find an existing item, set the flag to true
}
if (!found){ // if the item isn't found, we can add it to the cart.
// add item to cart
}
})
This structure might make a bit more sense - instead of using map, you can filter your cart.items array to find the matching product. If it's there, increment the quantity. If not, add it to the array or whatever it is that you need to do.
.then(cart => {
const existingItem = cart.items.filter(item => item.product == _idProduct)[0]
if(existingItem) existingItem.quantity += quantity
else {
//item does not exist, do what you need to do
}
cart.save(err => console.log(err));
res.json({
cart: cart
})
})

How to get data from a mongoose aggregate query in nodejs

The mongoose query is fetching the correct results but i am unable to access the data.
I want to get the current max id in the database, increment the value by 1 and save the data to the database
// add a new book
router.route('/books/add').post(jsonParser, (req, res) => {
Book.aggregate([{ $group: { _id: "id", id: { $max: "$id" } } }], (err, books) => {
if (!books)
return next(new Error("Could not load book!"))
else {
let book = new Book(req.body);
// log the max id returned, currently returns undefined
console.log("Id found ", books.id);
book.id = books.id + 1;
console.log("BookId ", book.id);
res.status(200).json({ 'book': 'Added successfully!' });
// book.save().then(book => {
// res.status(200).json({ 'book': 'Added successfully!' });
// }).catch(err => {
// res.status(400).send('Failed to create new record.');
// });
}
});
});
console.log(books.id) returns undefined whereas console.log(books) does show me the result which is [ { _id: 'id', id: 5 } ]. What i want to do is get the id i.e. 5, how do i do that ? Thank you.
The aggregate query result is an array,, and you have to use the zero index to get the first element which is an object contains your result. Try this:
console.log(books[0].id)

Resources