user3630457
user3630457

Reputation: 39

Mongoose Following/Followers

I am attempting to add a following/followers feature for a nodejs/mongoose driven website. I am having issues with getting the ID's stored properly using the methods below. Not quite sure what is going wrong but it appears to be saving only the ID properly to the following portion but not updating the followers for the first portion.

I know it would be easy if the user ID is just passed to the post request, but I thought storing the user ID on the front-end is kind of a security issue so just using the username to get the ID would be better.

// Handles the post request for following a user
router.post('/follow-user', function(req, res, next) {

  // First, find the user from the user page being viewed
  User.findOne({ username: req.body.username }, function(err, user) {

    // Add to users followers with ID of the logged in user
    user.followers = req.user._id;

    // Create variable for user from page being viewed
    var followedUser = user._id;

    // Save followers data to user
    user.save();

    // Secondly, find the user account for the logged in user
    User.findOne({ username: req.user.username }, function(err, user) {

      // Add the user ID from the users profile the follow button was clicked
      user.following = followedUser;

      // Save following data to user
      user.save();
    });
  });
});

The user model looks as such

var userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  email: { type: String, required: true },
  avatar: { type: String },
  bio: { type: String },
  following: [{ type: Schema.ObjectId, ref: 'User' }],
  followers: [{ type: Schema.ObjectId, ref: 'User' }],
});

Any insight on this would be greatly appreciated.

Upvotes: 2

Views: 4596

Answers (2)

Smakosh
Smakosh

Reputation: 1135

I managed to implement the follow/unfollow feature on my Node REST API by setting two routes instead of one:

For the Follow request:

  1. I check if the ID is a valid one
  2. I check if your ID doesn't match the id of the user you want to follow
  3. I add the ID of the user you want to follow in following array
  4. I add your ID to the followers array of the user you want to follow

For the Unfollow request:

  1. I check if the ID is a valid one
  2. I check if your ID doesn't match the id of the user you want to unfollow
  3. I remove the ID of the user you want to unfollow from following array
  4. I remove your ID from the followers array of the user you want to unfollow

authenticate is my custom middleware

id params is the ID of the user you're trying to follow/unfollow

res.user is returned from the "authenticate" middleware, please do not my answer.

router.patch('/follow/:id', authenticate, async (req, res) => {
    try {
        const id = new ObjectID(req.params.id)

        // check if the id is a valid one
        if (!ObjectID.isValid(req.params.id)) {
            return res.status(404).json({ error: 'Invalid ID' })
        }

        // check if your id doesn't match the id of the user you want to follow
        if (res.user._id === req.params.id) {
            return res.status(400).json({ error: 'You cannot follow yourself' })
        }

        // add the id of the user you want to follow in following array
        const query = {
            _id: res.user._id,
            following: { $not: { $elemMatch: { $eq: id } } }
        }

        const update = {
            $addToSet: { following: id }
        }

        const updated = await User.updateOne(query, update)

        // add your id to the followers array of the user you want to follow
        const secondQuery = {
            _id: id,
            followers: { $not: { $elemMatch: { $eq: res.user._id } } }
        }

        const secondUpdate = {
            $addToSet: { followers: res.user._id }
        }

        const secondUpdated = await User.updateOne(secondQuery, secondUpdate)

        if (!updated || !secondUpdated) {
            return res.status(404).json({ error: 'Unable to follow that user' })
        }

        res.status(200).json(update)
    } catch (err) {
        res.status(400).send({ error: err.message })
    }
})

router.patch('/unfollow/:id', authenticate, async (req, res) => {
    try {
        const { id } = req.params

        // check if the id is a valid one
        if (!ObjectID.isValid(id)) {
            return res.status(404).json({ error: 'Invalid ID' })
        }

        // check if your id doesn't match the id of the user you want to unfollow
        if (res.user._id === id) {
            return res.status(400).json({ error: 'You cannot unfollow yourself' })
        }

        // remove the id of the user you want to unfollow from following array
        const query = {
            _id: res.user._id,
            following: { $elemMatch: { $eq: id } }
        }

        const update = {
            $pull: { following: id }
        }

        const updated = await User.updateOne(query, update)

        // remove your id from the followers array of the user you want to unfollow
        const secondQuery = {
            _id: id,
            followers: { $elemMatch: { $eq: res.user._id } }
        }

        const secondUpdate = {
            $pull: { followers: res.user._id }
        }

        const secondUpdated = await User.updateOne(secondQuery, secondUpdate)

        if (!updated || !secondUpdated) {
            return res.status(404).json({ error: 'Unable to unfollow that user' })
        }

        res.status(200).json(update)
    } catch (err) {
        res.status(400).send({ error: err.message })
    }
})

Upvotes: 7

Ravi Shankar Bharti
Ravi Shankar Bharti

Reputation: 9268

From what i can see in your schema, following and followers and an array of ObjectId's and not ObjectId itself, so you need to push the _id into the array and not set its value to _id.

Also, do the second update in the callback of the save. this way you can send response back to the frontend after both the updates are done successfully.

Try this:

User.findOne({ username: req.body.username }, function(err, user) {

    user.followers.push(req.user._id);
    var followedUser = user._id;
    user.save(function(err){
        if(err){
            //Handle error
            //send error response
        }
        else
        {
            // Secondly, find the user account for the logged in user
            User.findOne({ username: req.user.username }, function(err, user) {

                user.following.push(followedUser);
                user.save(function(err){
                    if(err){
                        //Handle error
                        //send error response
                    }
                    else{
                        //send success response
                    }
                });
            });
        }
    });
});

I hope this helps you!

Upvotes: 4

Related Questions