Proper way to two .save() in mongoose - node.js

I am trying to save to two different docs and two different models in the same function, but no matter what I try I seem to get weird errors. It seems like for whatever reason mongoose has made this exclusively not work.
I have two findOne functions nested one is finding the book while the other is finding the prof and the object is to update them both to relate to each other.
Is there a recommended way I should do this perhaps two seperate backend endpoints and two seperate functions? that would be one solution to this problem, but I'd like to know why I cannot do whats below.
await prof.save().then(async () => {
await book
.save()
.then(() => {
return res.status(200).json({
success: true,
message: 'items updated'
}).catch( (err) => {
return res.status(400)
})
})
})

first of all you can not using await and .then .catch together. if you want update two or more collection in mongoose it's better use transactions, it's like rollback in relational database, but if you are newbie in mongoose it's hard to implement transaction,
without transaction you can do like this
try {
await prof.save();
await book.save();
return res.status(200).json({
success: true,
message: "items updated",
});
} catch (error) {
return res.status(400)
}

You probably want to have a look at Mongoose's documentation on middleware, especially the part about pre .save() middleware.
What you want to do is to trigger some function each time an item is saved. Particularly, you want this function to be another save function itself. This needs to be done in your Profs Schema, and you need to require your Books model to make it work:
ProfsSchema.pre('save', async function (next) {
const prof = this;
try {
await Books.findOneAndUpdate({ ... });
next();
} catch (e) {
throw e;
}
})
Of course, this is a very schematic suggestion: the { ... } represents whatever updates you want to apply to your book document. Also, you may want to do different things whether a new prof document is being created with a if (prof.isNew) or only trigger these functions when certain fields have been modified if (prof.field.isModified).
In any case, I strongly suggest you learn to use Mongoose's middleware for this kind of purpose: it's a rather powerful tool to interconnect different models across a MongoDB cluster.

Related

Best practice for validating input in Express/Mongoose app (controllers and models)

I'm new to node/express/mongoose and backend in general, and I'm building an API from the ground up.
I split my code into controllers and DAOs (mongoose models) and I'm not sure where validation should be taking place, what exactly should the controller and model each be doing?
For example, for the route GET /users/:id, I want it to:
Return 400 if the id given is not a valid ObjectId
Return 404 if the id is a valid ObjectId, but no documents exist with this id
Return 200 if a document is found, and remove some fields (password, __v, and _id (because I made a virtual field "id" without underscore)) before sending the response
Return 500 otherwise
Here's what I tried. It's currently doing everything I want above, but I'm not sure if it's the best implementation:
users.controller.js
const UserModel = require('../models/users.model')
const mongoose = require('mongoose')
exports.getById = async (req, res) => {
// Check that the id is a valid ObjectId
if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
return res.status(400).json({ error: 'Invalid ObjectID' })
}
try {
const user = await UserModel.findById(req.params.id)
if (!user) return res.status(404).json({ error: 'No user with this ID' })
res.status(200).send(user)
} catch (err) {
res.status(500).send(err)
}
}
users.model.js
exports.findById = async (id) => {
let user = await User.findById(id)
if (!user) {
return null
}
user = user.toJSON()
delete user._id
delete user.__v
delete user.password
return user
}
Is this the best way to structure things? Please critique and suggest any improvements and best practices.
I prefer to do all validation and logic in Controllers file and helpers classes in separate small js files.
Also trying to keep the models as lean as possible. Actually you can create different middleware functions to validate some inputs.
It is also helpful to create ErrorResponse middleware so you can do such simple and easy to read validations as:
if (!userFromDB) throw new ErrorResponse({code: 404, msg: "User doesn't exist"})
than catch it and handle in separate file with different options of the answer.
Backend has 100+ ways of implementing it. You need to choose you own 😁

Mongoose subdocument update: dot notation assignment OR .findByIdAndUpdate() [duplicate]

I am using .pull to remove a record from an array in mongo db and it works fine, but a comment I read somewhere on stack overflow (can't find it again to post the link) is bothering me in that it commented that it was bad to use .save instead of using .findByIdAndUpdate or .updateOne
I just wanted to find out if this is accurate or subjective.
This is how I am doing it currently. I check if the product with that id actually exists, and if so I pull that record from the array.
exports.deleteImg = (req, res, next) => {
const productId = req.params.productId;
const imgId = req.params.imgId;
Product.findById(productId)
.then(product => {
if (!product) {
return res.status(500).json({ message: "Product not found" });
} else {
product.images.pull(imgId);
product.save()
.then(response => {
return res.status(200).json( { message: 'Image deleted'} );
})
}
})
.catch(err => {
console.log(err);
});
};
I think what they were saying though was it should rather be done something like this (an example I found after a google)
users.findByIdAndUpdate(userID,
{$pull: {friends: friend}},
{safe: true, upsert: true},
function(err, doc) {
if(err){
console.log(err);
}else{
//do stuff
}
}
);
The main difference is that when you use findById and save, you first get the object from MongoDB and then update whatever you want to and then save. This is ok when you don't need to worry about parallelism or multiple queries to the same object.
findByIdAndUpdate is atomic. When you execute this multiple times, MongoDB will take care of the parallelism for you. Folllowing your example, if two requests are made at the same time on the same object, passing { $pull: { friends: friendId } }, the result will be the expected: only one friend will be pulled from the array.
But let's say you've a counter on the object, like friendsTotal with starting value at 0. And you hit the endpoint that must increase the counter by one twice, for the same object.
If you use findById, then increase and then save, you'd have some problems because you are setting the whole value. So, you first get the object, increase to 1, and update. But the other request did the same. You'll end up with friendsTotal = 1.
With findByIdAndUpdate you could use { $inc: { friendsTotal: 1 } }. So, even if you execute this query twice, on the same time, on the same object, you would end up with friendsTotal = 2, because MongoDB use these update operators to better handle parallelism, data locking and more.
See more about $inc here: https://docs.mongodb.com/manual/reference/operator/update/inc/

using .save() vs findByIdAndUpdate() for removing item from array

I am using .pull to remove a record from an array in mongo db and it works fine, but a comment I read somewhere on stack overflow (can't find it again to post the link) is bothering me in that it commented that it was bad to use .save instead of using .findByIdAndUpdate or .updateOne
I just wanted to find out if this is accurate or subjective.
This is how I am doing it currently. I check if the product with that id actually exists, and if so I pull that record from the array.
exports.deleteImg = (req, res, next) => {
const productId = req.params.productId;
const imgId = req.params.imgId;
Product.findById(productId)
.then(product => {
if (!product) {
return res.status(500).json({ message: "Product not found" });
} else {
product.images.pull(imgId);
product.save()
.then(response => {
return res.status(200).json( { message: 'Image deleted'} );
})
}
})
.catch(err => {
console.log(err);
});
};
I think what they were saying though was it should rather be done something like this (an example I found after a google)
users.findByIdAndUpdate(userID,
{$pull: {friends: friend}},
{safe: true, upsert: true},
function(err, doc) {
if(err){
console.log(err);
}else{
//do stuff
}
}
);
The main difference is that when you use findById and save, you first get the object from MongoDB and then update whatever you want to and then save. This is ok when you don't need to worry about parallelism or multiple queries to the same object.
findByIdAndUpdate is atomic. When you execute this multiple times, MongoDB will take care of the parallelism for you. Folllowing your example, if two requests are made at the same time on the same object, passing { $pull: { friends: friendId } }, the result will be the expected: only one friend will be pulled from the array.
But let's say you've a counter on the object, like friendsTotal with starting value at 0. And you hit the endpoint that must increase the counter by one twice, for the same object.
If you use findById, then increase and then save, you'd have some problems because you are setting the whole value. So, you first get the object, increase to 1, and update. But the other request did the same. You'll end up with friendsTotal = 1.
With findByIdAndUpdate you could use { $inc: { friendsTotal: 1 } }. So, even if you execute this query twice, on the same time, on the same object, you would end up with friendsTotal = 2, because MongoDB use these update operators to better handle parallelism, data locking and more.
See more about $inc here: https://docs.mongodb.com/manual/reference/operator/update/inc/

Mongoose: Updating some/all fields in a Mongodb collection

A settings form is a good example.
On submit, the appropriate collection needs to be updated; and 1, some, none or all of the fields need to be updated.
Assuming I'm not mistaken, Mongoose:
Ignores fields that aren't defined in the schema.
Does some low-level validation on the field based on the schema (i.e. it will reject, (ignore?) a value that isn't of the correct type).
So does that mean that the following is advisable?
.put(function(req, res, next) {
if (mongoose.Types.ObjectId.isValid(req.params._id)) {
Collection.update({_id: req.params._id}, { $set: req.body}, function (err, collection) {
if (err) return next(err);
res.send(200, {success: true});
})
}else{
res.send(400, {success: false});
}
})
i.e. passing req.body directly into the update?
It certainly works, but I can't help but feel it's little lacking in validation/filtering? Is mongoose doing enough in its schema for this approach to be sufficient? Or should I be iterating over each expected field?
I'm happy to set up some tests but thought I'd check with the community and sanity check my approach - any suggested alternatives gratefully received.
As it happens this would be terrible practice as Collection.update bypasses Mongoose validation, so the two assumptions I was making are false in this case.

Returning objects from mongoose model

I have the following code in my User.js class:
exports.findUser = function(nameIn){
User.find({
name: nameIn
});
)};
How can I make it 'return' the users it found? Something is telling me that using "return" is not best practice on Node? I think I need to use callbacks but, how?
this is the code I am using to call the function:
var User = require("../models/user.js");
User.findUser(req.params.name, function(users) {
console.log(users);
});
In findUser you don't seem to be providing a place for that callback to go. The mongoose query itself also returns the document(s or error) to a callback to be handled.
If you wanted to modify your findUser to fit with how you seem to be using it:
exports.findUser = function(nameIn,callback){
User.find({name: nameIn}, function(err,user) {
if (err)
throw err;
callback(user)
});
)};
The results of mongoose's query (successful or not) are handed to the function in the arguments of the query (as a callback), which is in line with the asynchronous nature of Node. The other generally accepted methods of handling Node's non-blocking I/O are events and streams, with methods like promises being viable if somewhat controversial.
Mongoose itself uses callbacks in its guides, but also has promises available.
Looking at your function now, you're handing it the name to query, and what you'd like to do with the returned documents as a callback. The mongoose function itself already has the capability of doing everything:
User.find({name: req.params.name}, function(err,user) {
if (err)
throw err;
console.log(user);
});
Defining findUser may only be worthwhile if you have a lot of things to play with in that document that findUser will keep DRY.

Resources