skip to Main Content

I have two separate models: Post and Comment. The Comment model has a field called ‘post’ which refers to a post object. I’m using pre hook in the post model to delete all the comments associated with the post. I’m trying to delete a post from the client side, but only the post itself gets deleted, not the comments. I’m unable to figure out why this is happening.

This is the hook from post model

postSchema.pre("remove", async function (next) {
  try {
    const commentIds = this.comments.map((comment) => comment.toString());
    await this.model("Comment").deleteMany({
      _id: {
        $in: commentIds,
      },
    });
    next();
  } catch (err) {
    next(err);
  }
});

and this is the route callback function

const deletePost = async (req, res) => {
  try {
 
    await Post.findByIdAndRemove(req.params.id);

    res.status(200).json({
      message: "Post deleted successfully",
    });
  } catch (error) {
    res.status(500).json({
      message: "Error deleting post",
    });
  }
};

2

Answers


  1. The reason this isn’t working as expected is because of the method used to delete the Post document: findByIdAndRemove(). In Mongoose, this method does not trigger the remove middleware, and as a result, the associated comments aren’t being deleted.

    The solution to this problem is to first fetch the Post document using findById() and then call the remove() method on the fetched document. This will trigger the remove middleware, and the associated Comment documents will be deleted as expected.

    Here’s the updated funtion:

    const deletePost = async (req, res) => {
      try {
        const post = await Post.findById(req.params.id);
    
        if (!post) {
          return res.status(404).json({
            message: "Post not found",
          });
        }
    
        await post.remove();
        
        res.status(200).json({
          message: "Post and associated comments deleted successfully",
        });
      } catch (error) {
        res.status(500).json({
          message: "Error deleting post",
        });
      }
    };
    
    

    Now, when you delete a post from the client side, both the post and its associated comments will be deleted.

    Login or Signup to reply.
  2. From Model.findByIdAndRemove() documentation:

    This function triggers the following middleware. findOneAndRemove()

    So you should use schema.pre('remove') hook. One use case of the pre middleware is:

    removing dependent documents (removing a user removes all their blogposts)

    And,

    In mongoose 5.x, instead of calling next() manually, you can use a function that returns a promise. In particular, you can use async/await.

    Besides, the elements of this.comments are all instances of mongoose.Types.ObjectId class. You can pass the this.comments as the value of $in operator directly.

    The below statement is unnecessary

    const commentIds = this.comments.map((comment) => comment.toString())
    

    Mongoose also support implicit $in:

    Because of schemas, Mongoose knows what types fields should be, so it can provide some neat syntactic sugar. For example, if you forget to put $in on a non-array field, Mongoose will add $in for you.

    Shorter way:

    models/post.js:

    import mongoose from 'mongoose';
    
    const PostSchema = new mongoose.Schema({
      title: String,
      comments: [{
        type: mongoose.Schema.Types.ObjectId, ref: 'comment'
      }]
    });
    
    PostSchema.pre('remove', async function () {
      await this.model('comment').deleteMany({ _id: this.comments })
    })
    
    export default mongoose.model('post', PostSchema);
    

    models/comment.js:

    import mongoose from 'mongoose';
    
    const CommentSchema = new mongoose.Schema({
      content: String,
    });
    
    export default mongoose.model('comment', CommentSchema);
    

    main.js:

    import Post from './models/post';
    import Comment from './models/comment';
    import mongoose from 'mongoose';
    import { config } from '../../src/config';
    
    mongoose.connect(config.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false });
    const db = mongoose.connection;
    db.on('error', console.error.bind(console, 'connection error:'));
    db.once('open', async () => {
      try {
        // seed
        // @ts-ignore
        const comments = await Comment.create([{ content: 'comment a' }, { content: 'comment b' }])
        const post = new Post({ title: 'post a', comments });
        await post.save();
    
        // test
        const postDoc = await Post.findOne({ title: 'post a' })
        await postDoc.remove();
    
      } catch (error) {
        console.log(error);
      } finally {
        db.close();
      }
    })
    

    Execution result:

    Mongoose: comments.insertOne({ _id: ObjectId("646338b558d2f850c15d80de"), content: 'comment a', __v: 0}, { session: null })
    Mongoose: comments.insertOne({ _id: ObjectId("646338b558d2f850c15d80df"), content: 'comment b', __v: 0}, { session: null })
    Mongoose: posts.insertOne({ comments: [ ObjectId("646338b558d2f850c15d80de"), ObjectId("646338b558d2f850c15d80df") ], _id: ObjectId("646338b558d2f850c15d80e2"), title: 'post a', __v: 0}, { session: null })
    Mongoose: posts.findOne({ title: 'post a' }, { projection: {} })
    this.comments:  ["646338b558d2f850c15d80de","646338b558d2f850c15d80df"]
    [true,true]
    Mongoose: comments.deleteMany({ _id: { '$in': [ ObjectId("646338b558d2f850c15d80de"), ObjectId("646338b558d2f850c15d80df") ] }}, {})
    Mongoose: posts.deleteOne({ _id: ObjectId("646338b558d2f850c15d80e2") }, RemoveOptions { session: null })
    

    package version: "mongoose": "^5.11.9"

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search