Search code examples
mongodbmongoosemongoose-schemamongoose-populate

How to skip removed ref while using populate() in Mongoose?


There is a MyCollection schema with:

...
author: {
    type: mongoose.Schema.Types.ObjectId,
    required: true, // the same with false
    ref: 'Author'
},
...

And the code:

MyCollection
    .find(filter, '_id author text')
    .populate('author', '_id name')
    .exec(),

If the document with author was removed from Authors collection, the populate method above throws the following error:

Cannot read properties of null (reading '_id')

The question is: How can populate() be configured to ignore removed ref's and simply return the document without the author field?


Solution

  • I don't think ignoring removed refs is a good idea. It would be better to remove the ref if the subdocument author has been deleted. The Mongoose default behaviour is to return null when there's no document, hence the error message you received. Mongoose provides you with two helper methods to check if the field is populated. You could try to incorporate:

    // Returns a truthy value if author populated   
    MyCollection.populated('author'); 
    
    
    // Make author not populated anymore
    MyCollection.depopulate('author');
    

    However, I would suggest you refactor your code so that when a document from author collection is deleted you pass the author._id of the deleted document so that it can be pulled from the MyCollection. Something like:

    const deletedAuthor = await authorModel.findByIdAndDelete(authorid);
    const myCollection = await myCollectionModel.findById(parentId);
    myCollection.author.pull(authorid);
    await myCollection.save();
    

    Also, you don't need to include _id in your .populate('author', '_id name') projection because according to the docs the _id field is returned as default on matching documents.

    You can remove null fields after your query using basic JavaScript but you need to change exec() to the lean() method so that POJO are returned like so:

    const myCollection = await MyCollection
    .find(filter, '_id author text')
    .populate('author', '_id name')
    .lean();
    
    const myCollectionFiltered = myCollection.map((doc)=>{
       if(doc.author === null){
          delete doc.author;
       }
       return doc;
    });
    

    Now myCollectionFiltered contains no null values for author.