Tony mpt
Tony mpt

Reputation: 1

Cannot do populate() with mongoose

I am new to MongoDB and Mongoose and I want to perform a populate() between two documents. After consulting the documentation and searching for answers, I am turning to you for help. I have two models:

Note model

const mongoose = require('mongoose');

const noteSchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    title: { 
        type: String, 
        required: true 
    },
    color: { 
        type: String, 
        required: true 
    },
    position: { 
        type: Number, 
        required: true 
    },
    isCheckBoxMode: { 
        type: Boolean,
        required: true 
    },
    items: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Item'
    }]
}, {timestamps: true });

const Note = mongoose.model('Note', noteSchema);

module.exports = Note;

Item Model

const mongoose = require('mongoose');

const itemSchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    text: {
        type: String, 
        required: true
    },
    isCompleted: {
        type: Boolean, 
        required: true
    },
    noteId: { 
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Note',
        required: true
    },
}, {timestamps: true });

const Item = mongoose.model('Item', itemSchema);

module.exports = Item;

In this query, I would like to retrieve an array of all the items linked to the noteId, using the "items" property, which refers to the Item document. As you can see above, I have made sure to reference between the two documents, on one hand with the items property and its ref Item, and on the other hand with the NoteId property whose id corresponds to the correct note and refers to the Note document.

It's better to have an example. I am using Mongo Atlas to quickly deploy a database. I created a note with Postman whose id is "6553448ef2c06064f266f785":

Note document

and two items each referring to noteId "6553448ef2c06064f266f785":

Item document

Unfortunately, when I send my request with populate('items') to retrieve the content of my items, I receive an empty array:

router.get('/:noteId', (req, res, next) => {
    const id = req.params.noteId;
    Note.findById(id)
        .populate('items')
        .exec()
        .then(doc => {
            if (doc) {
                res.status(200).json({
                    notes: doc,
                });
            }else {
                res.status(404).json({message : 'No Valid entry found for provided ID'});
            }
        })
        .catch(err => {
            console.log(err);
            res.status(500).json({error: err});
        });
});

The request and response in Postman

request

I found another solution, using "virtuals", but it is not the most optimized solution and I am afraid of losing performance.

const mongoose = require('mongoose');

const noteSchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    title: { 
        type: String, 
        required: true 
    },
    color: { 
        type: String, 
        required: true 
    },
    position: { 
        type: Number, 
        required: true 
    },
    isCheckBoxMode: { 
        type: Boolean,
        required: true 
    },
}, {timestamps: true });

noteSchema.virtual('items', {
    ref: 'Item',
    localField: '_id',
    foreignField: 'noteId'
});

noteSchema.set('toObject', { virtuals: true });
noteSchema.set('toJSON', { virtuals: true });

const Note = mongoose.model('Note', noteSchema);

module.exports = Note;

Sorry if my question is a bit stupid, but I searched for a long time and couldn't find any solutions. thank you in advance for your help

I tried to link the two documents following the Mongoose documentation as well as other Stack Overflow posts, but it didn't work. Also, I set up a 'virtual' which worked and allows me to retrieve all the items, but this is not optimized. Also, when I add item with a POST route, if in my request I also add the item's id to the 'items' array of the note, the populate works. But this is also not an optimized solution. I would like to do a simple populate between two 'refs'.

Upvotes: 0

Views: 84

Answers (1)

jQueeny
jQueeny

Reputation: 8027

Your Note schema looks fine. You have an items property that has been correctly defined as an array of mongoose.Schema.Types.ObjectId with the corresponding ref:'Note' so that looks textbook.

My advice would be to explicitly pass the path and model properties to the populate method and adopt async/await pattern like so:

router.get('/:noteId', async (req, res, next) => { //< Mark as async
   try{
      const id = req.params.noteId;
      const doc = await Note.findById(id).populate({ //< use await 
         path: 'items', model: Item //< pass in the path and model
      });
      if(!doc){
         return res.status(400).json({
            message : 'No Valid entry found for provided ID'
         });
      }else{
         return res.status(200).json({
            notes: doc,
         });
      } 
   }catch(err){
      console.log(err);
      res.status(500).json({error: err});
   }
});

Note: please ensure that there are ObjectId values in the items array. It is not clear from your compass screenshot because the items array is collapsed in the display. Lastly, your instrincts are correct and you don't need virtuals for this.

Upvotes: 0

Related Questions