MongoDB troubles with finding and modifying documents based on the same ID - node.js

I've been experimenting with nosql databases recently, mostly mongodb and I came to a weird blockage. To keep it really short I started implementing user auth / profile creation. User and profile have a 1-1 relationship. When a user is created, so is the profile but with no values at the start except for the user id
user = new User({
username,
email,
password
})
user.password = await bcrypt.hash(password, salt)
const newUser = await user.save()
const newProfile = await new Profile({user: newUser.id})
await newProfile.save()
So this works just fine, I even added some checks to compare the newUser.id and newProfile.user, it's a match, they're identical
Now the problem arises when I try to update the profile in this code here, for some reason I think mongodb doesn't think the id values are the same, I've been going around this for a while and can't figure out what I'm doing wrong. So this piece of code returns NULL to my profile update endpoint and I have no idea why ( I'm using mongoose )
try {
const profile = await Profile.findOneAndUpdate(
{user: req.user.id},
{$set: profileData},
{new: true})
return res.json(profile)
} catch (err) {
console.log(err.message)
res.status(500).send("Server error")
}
I was thinking if this doesn't work out, I could just generate an id before making a doc and passing it to the collections as a custom field
There are no errors

Related

How do I delete all posts associated with a user in my node application using mongodb

So, I was wondering why deleteMany() isn't working for me here. I have a node application that uses mongodb as db and Mongoose along with expressjs.
I have a function that delete a user and I would like to delete the user's posts, comments, replies, that have that user's id.
I used deleteMany() and passed the user's mongodb _id. But this has refused to work. It is not deleting the user's posts, comments and replies. The user gets deleted but the associated contents are not getting deleted and not returning any error. What is the best way to go about this? I have searched on google but couldn't find anything helpful so far. Here is the function code:
const deleteUser = async (req, res) => {
if (req.user.userId === req.params.id || req.user.role === 'admin') { //we checked if the user id matched
try {
const user = await User.findById(req.params.id) //get the user and assign it to user variable
if (!user) {
return res.status(200).json("User not found")
}
await Post.deleteMany({
_id: user._id
}) //deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({
author: user._id
}) //delete user's comment by checking the comment author's id.
await Reply.deleteMany({
author: user._id
}) //deletes user's replies
await User.findByIdAndDelete(req.params.id) //delete the user
res.status(200).json("User has been deleted")
} catch (err) {
res.status(404).json("User not found")
}
} else {
res.status(401).json("You can only delete your account!")
}
}

I have two models in MongooseDB, how do I update the two at once using Node.js and React.js?

I have two models named user and post in my Mongoose database. I will like that when I update the user model, it reflects on the post model. For instance, I updated my username from the user model, I would like that update to take effect on the post model as well. I am using Node.js and React.
here is my code on Node.js for the user model
router.put("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){//we checked if the user id matched
if(req.body.password){
const salt = await bcrypt.genSalt(10);
req.body.password = await bcrypt.hash(req.body.password, salt);
}
try{
const updatedUser = await User.findByIdAndUpdate(req.params.id,{
$set: req.body,
}, {new: true});
//findbyidandupdate is an inbuilt method
res.status(200).json(updatedUser)
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
} else{
res.status(401).json("You can only update your account!")
}
});
I am assuming that you want to have user information attached with each post and when the user data is modified, the changes should reflect on subsequent requests for post data as well.
The functionality you are looking for is mongoose refs and populate.
These allow you to have reference to documents in a document.
In your case, we want a ref to a user document inside a post document.
So the schema for Post model could look like this:
const postSchema = new mongoose.schema({
...
user: { type: Schema.Types.ObjectId, ref: 'User' } // Here 'User' is the name of the UserModel
...
})
Now each post will contain a reference of a user.
The data that is being saved on each post doc as the user property is the _id of a user doc (not the complete document). So you can update the user docs without worrying about how it's going to affect the posts.
For retrieving the user information each time you retreive a post, you need to populate the post doc.
const post = await Post.
findOne(...).
populate('user').exec()
More information: https://mongoosejs.com/docs/populate.html

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 😁

I need to update only user name in my app using Node.js and MongoDB

async function update(id, userParam) {
const user = await User.findById(id);
// validate
if (!user) throw 'User not found';
if (user.username !== userParam.username && await User.findOne({ username: userParam.username })) {
throw 'Username "' + userParam.username + '" is already taken';
}
// hash password if it was entered
if (userParam.password) {
userParam.hash = bcrypt.hashSync(userParam.password, 10);
}
// copy userParam properties to user
Object.assign(user, userParam);
await user.save();
}
When I send an update request it updates everything in the body. I only need to send userid, username and user role and update to update only those fields. I am very beginner.PLEASE help me to solve my issue
You can find the document and update the field what you want by using "updateOne". for example, here I am going to update User collection's username only. mongodb's "updateOne" will fulfill that task here basically updateOne has 3 parameters.
search the document you can use any filed here I have used the _id field.
{ "_id": "232323"}
second parameater is {$set:{"username":"kalo"} this will set the new value.here you can use any number of fields.
User.updateOne({ "_id": "232323"},{$set:{"username":"kalo"}})
For More mongodb doc
If you are using Mongoose nodejs mongodb ORM read this. This doc says the same thing that I explained.
In your case you can simply use like below instead of await user.save();
await User.findOneAndUpdate({ _id: userid }, { username : username , userid: userid,userrole:userrole})
use the User not user
user.username = user.userParam;
This is the key Line I needed.It solved my issue....Although it's simple answer,it may be useful to some of the beginners like me.

Data is mutated but not updated in the database

I have a sever connected to a mongodb database. When I add a first level data and then save that, it works.
For example :
// this works fine
router.post('/user/addsomedata', async (req,res)=>{
try {
const user = await User.findOne({email : req.body.email})
user.username = req.body.username
await user.save()
res.send()
} catch(e) {
res.status(404).send(e)
}
})
BUT if I try to save the object with deeper level data, it's not getting saved. I guess the update is not detected and hence the user didn't get replaced.
Example :
router.post('/user/addtask', auth ,async (req,res)=>{
const task = new Task({
name : req.body.name,
timing : new Date(),
state : false,
})
try {
const day = await req.user.days.find((day)=> day.day == req.body.day)
// day is found with no problem
req.user.days[req.user.days.indexOf(day)].tasks.push(task)
// console.log(req.user) returns exactly the expected results
await req.user.save(function(error,res){
console.log(res)
// console.log(res) returns exactly the expected results with the data filled
// and the tasks array is populated
// but on the database there is nothing
})
res.status(201).send(req.user)
} catch(e) {
res.status(400).send(e)
}
})
So I get the tasks array populated on the console even after the save callback but nothing on the db image showing empty tasks array
You're working on the user from the request, while you should first find the user from the DB like in your first example (User.findOne) and then update and save that model.
Use .lean() with your find queries whenever you are about to update the results returned by mongoose. Mongoose by default return instance objects which are immutable by nature. lean() method with find returns normal js objects which can be modified/updated.
eg. of using lean()
const user = await User.findOne({email : req.body.email}).lean();
You can read more about lean here
Hope this helps :)

Resources