For example I want to update a mongoose document in a put request, I have to do this:
app.put('/update', async(req,res) => {
try{
const product = await Product.findById(req.body.id)
product.name = req.body.name
product.price = req.body.price
procut.discount = req.body.discount
// etc...
await product.save()
res.json(product)
}catch(e){
res.json({message: "Error updating the product"})
}
})
I'm asking if there is another faster and developer friendly way of updating products instead of typing each of the document properties and equal them to the req.body.[property]?
You can try the following for object merging
Object.assign(product, req.body)
note: i haven't tried with mongoose collection
You can use updateMany or findOneAndUpdate model methods, but it is more advisable to use .save()
https://mongoosejs.com/docs/api.html#model_Model.updateMany
https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
If you want to .save() to look cleaner, you can do like this:
async updateEntity(payload) {
const keysToUpdate = Object.keys(payload)
if (keysToUpdate.length === 0) {
throw new Error('Update payload must not be empty!')
}
const entity = await entityModel.findOne({ _id: redirect })
keysToUpdate.forEach((key) => {
entity[key] = payload[key]
})
await entity.save()}
Related
This is the basic structure of the Schema I am working with using mongoose:
const User = {
uid: {
type: String
},
routes: {
type: Array
}
}
In my application there is a POST to /route, in which uid and a new route are provided as "body parameters". In order to add to the routes array, I wrote a code similar to this (the only diference is that I check if the route already exists):
var user = await User.find({uid: uid}) // user is found, as expected
user[0].routes.push(route //parameter)
user.save()
When a POST request is made, though, it throws an error:
TypeError: user.save is not a function
What am I doing wrong?
user in your code is an array of documents
so you'll have mongo documents inside that array
you can't do array.save, you've to do document.save
await user[0].save()
var user = await User.find({uid: uid}) // user is found, as expected
if (user && user.length) {
user[0].routes.push(route //parameter)
await user[0].save(); // save the 1st element of the object
}
if your query returns only 1 record better use https://mongoosejs.com/docs/api.html#model_Model.findOne
var user = await User.findOne({uid: uid}) // user is found, as expected
if (user) {
user.routes.push(route //parameter)
await user.save(); // save the 1st element of the object
}
if you need to find only one specific user you should use findOne function instead
User.findOne({uid: uid})
.then(
(user) => {
user[0].routes.push(route //parameter);
user.save();
},
(err) => {
console.error(err);
}
)
I think bulkSave() can be what you're looking for:
var user = await User.find({uid: uid}
enter code user[0].routes.push(route //parameter)
await User.bulkSave(user)
The problem is probably simple, but my 2 AM brain can't understand what's going on anymore. I'm trying to create a profile page that shows basic public info. The way I'm trying to make it work is pulling out the users username from mongodb when registered the account by his specific _id. If needed, verification I use is JWT.
app.post('/api/user-profile', async (req,res) => {
const { token } = req.body
if(!token) {
return res.json({ status: 'error', error: 'not logged in' })
}
try {
const user = jwt.verify(token, JWT_SECRET)
const userid = user.id
const result = User.findOne({ userid })
console.log(result)
// return res.json({ status: 'ok', name: result })
} catch(error) {
// return res.json({ status: 'error', error: 'something went wrong' })
console.log(error)
}
})
I'm not sure what function should I use, findOne() or findById(). I tried to look at the documentation at mongoose, but the explanation is a bit too hard for me to understand.
P.S User = the user registration model. If needed I can paste in the code.
use findById instead of findOne if userid is _id and use await before the query, so do like this:
const result = await User.findById(userid)
if you want to use findOne :
const result = await User.findOne({"_id" : userid})
if you want a plain object javascript use .toObject after query like this:
const result = await User.findById(userid).toObject()
console.log(result)
I don't have a lot of experience with mongoose but worked with Mongo quite a lot. findOne is a direct correspondent of Mongo's findOne function which receives a query in the format {"key": "expectedValue"}. If you want to use it to get data by id the query is {"_id": user.id}.
Because fetching data by id is a common case, the lib added the method findByID which receives an ID, and then formats the query and makes an internal call to findOne.
For anyone interested, the answer is just like Mohammad Yaser Ahmadi said. Everything works fine, and by getting the username I did:
const user = jwt.verify(token, JWT_SECRET)
const userid = user.id
const result = await User.findById(userid)
const usersName = result.username
console.log(usersName)
I'm having an conceptual issue on how to update all instances of a model once I have updated it.
Imagine the following method renameUser (could be any ORM):
async function renameUser(userId: number, newUsername: string) {
const user = await User.findByPk(userId);
await user.update({ username: newUsername });
}
And the following usage:
const user = await User.create({ username: "old" });
// user.username === "old"
this.renameUser(user.id, "new");
// still, user.username === "old"
Obviously this problem wouldn't exist if I would pass the user object directly into the update method, but that only works in this simple example - In my case it is actually not possible to share the same instance, since it can be modified via hooks in an entirely different context.
So, one simple solution would be to call user.reload() after the call, which will pull the latest user data from the database:
const user = await User.create({ username: "old" });
// user.username === "old"
this.renameUser(user.id, "new");
user.reload();
// now: user.username === "new"
However, this requires me to know that the renameUser method will change my user object. In this case it is obvious, but if the method is called that's not always possible.
Is there any pattern I can use to work around this? One thing which came in my mind was to create a UserFactory which ensures that I only have one instance of a user (indexed by its primary key) at any time, and then update that instance. But I was wondering how others solve it? Is it a common problem?
Why you dont use .save() in updateUser function?
const renameUser = async (userId, newUsername) => {
const user = await User.findByPk(userId);
user.username = newUsername;
await user.save();
return user;
}
and use it like this
const user = await User.create({ username: "old" });
// user.username === "old"
user = await this.renameUser(user.id, "new");
// now, user.username === "new"
You can run your queries more efficiently by just running an update using the Model.update() function instead of querying for an Instance and then updating it.
async function renameUser(userId: number, newUsername: string) {
const [ updatedRowCount, updateRowOnPostgresOnly ] = await User.update({
username: newUsername,
}, {
where: {
id: userId,
},
});
// you can use the updated row count to see if the user changed.
const isUpdated = updatedRowCount > 0;
return isUpdated;
}
Now you can await the result to see if the row changed, even without loading it.
const user = await User.create({ username: "old" });
// user.username === "old"
const isUpdated = await this.renameUser(user.id, "new");
if (isUpdated) {
user.reload();
// now: user.username === "new"
}
Note that in your example you are not using await on the async runameUser() function.
This varies from your question a bit, but if you are already working with Model instances then you can use Instance.changed() to get the changed fields.
const user = await User.create({ username: "old" });
user.username = "old";
const changed = user.changed(); // == [ "username" ]
if (changed.length) {
await user.save();
}
*Note that in that example it checks for changed.length but really Sequelize won't do anything if you await instance.save() and nothing is in instance.changed() - change awareness of save.
This is my first question here. I tried to save document in my collection, but it doesn't work. Response of function is exactly like I want, but it doesn't save in my db. In another controller (createRoom) foundUser.save() it works, but in this controller it doesn't. Thanks in advance!
I am using mongodb/mongooose and express.
const removeRoom = async (req,res,next) => {
const {roomId, userData} = req.body;
const { userId, token } = userData;
let foundUser;
let updatedRooms;
let indexOfNamespaces;
try {
foundUser = await User.findById(userId)
foundUser.namespaces.forEach((ns,i1)=>{
updatedRooms = ns.rooms.filter((room,i2) => {
if(room.id === roomId){
indexOfNamespaces = i1;
}
return room.id !== roomId
})
})
foundUser.namespaces[indexOfNamespaces].rooms = updatedRooms;
console.log(foundUser);
await foundUser.save();
} catch (err) {
console.log(err);
const error = new HttpError('Sth went wrong [removeRoom]', 500);
return next(error);
}
res.status(201).json({updatedNamespaces: foundUser.namespaces});
}
Mongoose does some optimizations where it will only actually save a field if it "changes". In this case you are modifyting an array, but the array is still the "same" array as in it still === (equals) the previous array. You need to use a new array to replace namespaces.
For example:
foundUser.namespaces = [
...foundUser.namespaces.slice(0, indexOfNamespaces),
{ ...foundUser.namespaces[indexOfNamespaces], rooms: updatedRooms },
...foundUser.namespaces.slice(indexOfNamespaces + 1)
]
Now, when you save Mongoose will see a "new" array that !== (does not equal) the previous array because it is a new instance and it will save it.
I know i have to use some but for some reason i cant seem to get it right. i have a collection in my mongodb database of posts. each post has an array of objects named "likes" that references the users that liked this post. so in my backend i want to check if the user exists in the likes array of the post. if it does not exist then like the post, else return with an appropriate message on my react frontend. The code i will include always returns false from some so a user can like a post infinite times.
exports.postLike = async (req, res, next) => {
const postId = req.query.postId;
const userId = req.query.userId;
console.log('postId: ' + postId);
try{
const post = await Post.findById(postId).populate('creator').populate('likes');
const user = await User.findById(userId);
if (!post.likes.some(post => post._id === user._id)){
post.likes.push(user);
console.log('liked a post');
const result = await post.save();
res.status(200).json({ message: 'Post liked!', post: result });
} else {
console.log('Post already liked!');
res.status(200).json({ message: 'Post already liked!', post: post });
}
}catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
}
};
i clearly haven't understood, yet, how some works so if you can help that would be great. also if you have any other solution that would be good in this case then please post it. i tried some random codes with indexOf and includes for checking but it didn't work either. i am not sure which is the right way to check if the user object is included in the "likes" array of objects. i would prefer not to write any function of my own to check this, i want to do it using an existing function/method provided by javascript.
Going to offer a different route here. You are fetching all the data including a join to the creator and likes just to add a like to the collection. This is a little wasteful and can be achieved by just doing an update and use $addToSet which will add the like if it does not exist.
You then just check nModified in the result to know if it was added or not. So you can have:
const result = await Post.updateOne(
{
id: 1
},
{
$addToSet: {
likes: {
userId: mongoose.Types.ObjectId(req.query.userId)
}
}
}
);
console.info(result.nModified === 1);
Alternatively, you can use some as follows using === to compare type and value:
posts.likes.some(like => like.userId.toString() === req.query.userId)
MongoDB.ObjectId is a wrapper around a primitve, just like Number or Boolean. And just like
new Boolean(true) === new Boolean(true)
will be false, your comparison will fail too. You have to take out the primitive for comparison:
post._id.valueOf() === user._id.valueOf()