javascriptnode.jsmongodbmongoosepopulate

How can I populated nested documents with their Virtuals with Mongoose?


I have following schema:

// foo.js
...

const fooSchema = new Schema(
  {
    name: {
      type: String
    }
  },
  {
    toJSON: {
      virtuals: true
    },
    toObject: {
      virtual: true
    }
  }
);

// sample virtual
fooSchema.virtual('sampleVirtual').get(function () {
  return 'I am a virtual'
});

export default mongoose.model( 'foo', fooSchema );

and another schema where I have a ref on the above schema:

// hello.js
...

const helloWorldSchema = new Schema(
  {
    name: {
      type: String,
      required: true
    },
    myFoo: {
      type: mongoose.Types.ObjectId,
      ref: 'foo'
    }
  },
  {
    toJSON: { 
      virtuals: true 
    },
    toObject: {
        virtual: true
    }
  }
);

export default mongoose.model( 'HelloWorld', helloWorldSchema );

I am now using Mongoose' findOne() method to query for a document of the helloWorldSchema collection and want to populate the myFoo document with it's virtual sampleVirtual:

// get.js
const retVal = await helloWorldModel
    .findOne({_id: <some-id> })
    .populate({ path: 'myFoo', select: '_id name sampleVirtual' })
    .exec();

console.log(retVal.myFoo.sampleVirtual) //undefined

Am I doing something wrong?

Best Valentin


Solution

  • Your code has some typos. The toJSON and toObject are schema options, and the options is the second parameter of the mongoose.Schema class constructor. The mongoose.Schema class constructor signature is:

    constructor(definition?: SchemaDefinition<LeanDocument<SchemaDefinitionType>>, options?: SchemaOptions);
    

    E.g. ("mongoose": "^7.3.1")

    import mongoose from 'mongoose';
    import { config } from '../../config';
    
    mongoose.set('debug', true);
    
    const fooSchema = new mongoose.Schema(
        {
            name: String,
        },
        {
            toJSON: { virtuals: true },
            toObject: { virtuals: true },
        },
    );
    fooSchema.virtual('sampleVirtual').get(function () {
        return 'I am a virtual';
    });
    const Foo = mongoose.model('foo', fooSchema);
    
    const helloWorldSchema = new mongoose.Schema(
        {
            name: String,
            myFoo: { type: mongoose.Schema.Types.ObjectId, ref: 'foo' },
        },
        {
            toJSON: { virtuals: true },
            toObject: { virtuals: true },
        },
    );
    const HelloWorld = mongoose.model('helloWorld', helloWorldSchema);
    
    (async function main() {
        try {
            await mongoose.connect(config.MONGODB_URI);
            // seed
            const [foo1] = await Foo.create([{ name: 'a' }, { name: 'b' }]);
            const [h1] = await HelloWorld.create([
                { name: 'x', myFoo: foo1._id },
                { name: 'y', myFoo: foo1._id },
            ]);
            const retVal = await HelloWorld.findOne({ _id: h1._id })
                .populate({ path: 'myFoo', select: '_id name sampleVirtual' })
                .exec();
            console.log(retVal);
        } catch (error) {
            console.error(error);
        } finally {
            await mongoose.connection.close();
        }
    })();
    

    Debug logs:

    Mongoose: foos.insertOne({ name: 'a', _id: ObjectId("649a7bdfbedaa852fa187538"), __v: 0 }, {})
    Mongoose: foos.insertOne({ name: 'b', _id: ObjectId("649a7bdfbedaa852fa187539"), __v: 0 }, {})
    Mongoose: helloworlds.insertOne({ name: 'x', myFoo: ObjectId("649a7bdfbedaa852fa187538"), _id: ObjectId("649a7bdfbedaa852fa18753c"), __v: 0}, {})
    Mongoose: helloworlds.insertOne({ name: 'y', myFoo: ObjectId("649a7bdfbedaa852fa187538"), _id: ObjectId("649a7bdfbedaa852fa18753d"), __v: 0}, {})
    Mongoose: helloworlds.findOne({ _id: ObjectId("649a7bdfbedaa852fa18753c") }, {})
    Mongoose: foos.find({ _id: { '$in': [ ObjectId("649a7bdfbedaa852fa187538") ], [Symbol(mongoose#trustedSymbol)]: true }}, { skip: undefined, limit: undefined, perDocumentLimit: undefined, projection: { _id: 1, name: 1, sampleVirtual: 1 }})
    {
      _id: new ObjectId("649a7bdfbedaa852fa18753c"),
      name: 'x',
      myFoo: {
        _id: new ObjectId("649a7bdfbedaa852fa187538"),
        name: 'a',
        sampleVirtual: 'I am a virtual',
        id: '649a7bdfbedaa852fa187538'
      },
      __v: 0,
      id: '649a7bdfbedaa852fa18753c'
    }
    

    The virtual field sampleVirtual is there.