Search code examples
node.jsarraysmongodbmongoose

why mongoose does not have regular array methods


Im new to mongoose and i have an array in my schema that is a nested document like the following:

const userSchema = new Schema({
    username: String
    friends: [friendsSchema],
});

const friendsSchema = new Schema({
    name: { type: String, unique: true },
});

lets say i want to add a friend to a specefic user, i need to do the following:

  • check if the user id is valid or not
  • check if the user id exists in the database or not
  • if the user exists check if he already have a friend that goes by that name
  • if he does not push into the array of friends a new friend

I initially believed that once I obtained the user, I could simply execute a foreach loop on the user.friends array to verify the friend's existence. However, it turns out that mongoose Arrays behave differently from regular JavaScript arrays. This was quite frustrating because it meant I would need to construct a complex query, potentially resulting in additional database reads, instead of directly accessing the user and checking the desired information from the user object. Consequently, I came up with the following implementation:

    let { name, userId } = req.body;
    try {
        //check if userId is a valid mongoose objectId
        if (!mongoose.Types.ObjectId.isValid(userId)) {
            res.status(404).json({ error: "invalid user id" });
        }
        // get the user
        let user = await userModel.findById(userId);
        if (!user) {
            res.status(404).json({ error: "user not found" });
        }
        
        let user = user.friends.foreach(friend =>{ 
                   if (friend.name === name)
                      {     
                        res.status(404).json({error: "friend already added to user"});
                      }
        }); 
        
        user.friends.push({ name});
        user.save();
        res.status(200).json(user);
  • is it possible to do all those previous steps in one single query, like one database read that does everything?

  • what was the rational of having a distinct Array type on mongoose?

PS: this is not a duplicate question of how to push and save to an array


Solution

  • You don't have to find the user and loop through of their friends. You can just do a conditional update and push the friend to the array if the condition is not met. For example:

    Delete from here and everything after it:

    let user = await userModel.findById(userId);
    

    Change to this:

    const user = await userModel.findOneAndUpdate(
       {
          _id: userId, //< Find a user with this id
          'friends.name': { $ne: name } //< But also can't have a friend already with this name 
       },
       {
          $push: { // If you find a user with that id and no existing friend then push
             "friends": {name: name}
          }
       },
       { new: true } //< This tells mongoose to give you back the user with the friend added i.e the updated version
    );
    if (!user) {
       res.status(404).json({ error: "user not found or friend already added" });
    } else{
       res.status(200).json(user);
    }
    

    If you really wanted to, you could still first check to see if the user existed with a quick:

    const existingUser = await userModel.findById(userId);
    

    Then based on the return value do the conditional update but your application really shouldn't be allowing your users to try and update a user that doesn't exist.