Search code examples
node.jsmongodbexpressmongoose

Mongoose not able to populate nested item


I am using express + mongoose and in general the populate method works fine. There is one case however, where it is not working. Neither does it show any errors:

I have an assignments model, which has some modules:

import { Model, Schema, model } from 'mongoose';
import { IAssignment } from '../interfaces/app/IAssignment';

// Define Mongoose schema for Assignment
const AssignmentSchema = new Schema<IAssignment>(
  {
    name: { type: String, trim: true, required: true },
    modules: [
      {
        module: { type: Schema.Types.ObjectId, ref: 'AssignmentModule' },
        dueDate: { type: Date },
      },
    ],
  },
);

// Create and export Mongoose model
const Assignment: Model<IAssignment> = model<IAssignment>(
  'Assignment',
  AssignmentSchema
);

export default Assignment;

and there is another model for AssignmentModule

import { IAssignmentModule } from '../interfaces/app/IAssignmentModule';
const Schema = mongoose.Schema;

// Define Mongoose schema for Module
const AssignmentModuleSchema = new Schema<IAssignmentModule>(
  {
    title: { type: String },
    questions: [{ type: Schema.Types.ObjectId, ref: 'Question' }],
  },
  { timestamps: true, versionKey: false }
);

// Create and export Mongoose model
const AssignmentModule: Model<IAssignmentModule> =
  mongoose.model<IAssignmentModule>('AssignmentModule', AssignmentModuleSchema);
export default AssignmentModule;

And this is my controller function, which should give me all assignments with modules.module populated.

const assignments: IAssignment[] = await Assignment.find().populate([
      { path: 'modules', populate: 'modules.module' },
    ]);

My eventual goal is to populate modules.module, and then inside the module, there is a questions reference as well, which needs to be populated with some selected fields, lets say name and score. I understand I can chain the populate, but my first population itself is not happening. Is there any issue naming your field as module? Or there is something else that is missing here?


Solution

  • The modules are subdocuments, see Alternate declaration syntax for arrays. The populate path should be modules.module.

    E.g.

    import mongoose from 'mongoose';
    import util from 'util';
    import { config } from '../../config';
    
    mongoose.set('debug', true);
    console.log(mongoose.version);
    
    const QuestionSchema = new mongoose.Schema({
        score: Number,
    });
    const Question = mongoose.model('Question', QuestionSchema);
    
    const AssignmentModuleSchema = new mongoose.Schema({
        title: { type: String },
        questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }],
    });
    const AssignmentModule = mongoose.model('AssignmentModule', AssignmentModuleSchema);
    
    const AssignmentSchema = new mongoose.Schema({
        modules: [
            {
                module: { type: mongoose.Schema.Types.ObjectId, ref: 'AssignmentModule' },
                dueDate: { type: Date },
            },
        ],
    });
    const Assignment = mongoose.model('Assignment', AssignmentSchema);
    
    (async function main() {
        try {
            await mongoose.connect(config.MONGODB_URI);
            await Promise.all([Question, Assignment, AssignmentModule].map((m) => m.collection.drop()));
            // seed
            const [q1] = await Question.create([{ score: 100 }, { score: 80 }]);
            const [m1] = await AssignmentModule.create([{ title: 'a', questions: [q1] }, { title: 'b' }]);
            await Assignment.create([{ modules: [{ module: m1, dueDate: Date.now() }] }, { modules: [] }]);
    
            // test
            const assignmentDocs = await Assignment.find().populate({
                path: 'modules.module',
                select: '-__v',
                populate: { path: 'questions', select: '-__v' },
            });
            console.log(util.inspect(assignmentDocs, false, null));
        } catch (error) {
            console.error(error);
        } finally {
            await mongoose.connection.close();
        }
    })();
    

    Debug logs:

    [
      {
        _id: new ObjectId("64a807003389777bbfe22f2f"),
        modules: [],
        __v: 0
      },
      {
        _id: new ObjectId("64a807003389777bbfe22f2d"),
        modules: [
          {
            module: {
              _id: new ObjectId("64a807003389777bbfe22f29"),
              title: 'a',
              questions: [
                {
                  _id: new ObjectId("64a807003389777bbfe22f25"),
                  score: 100
                }
              ]
            },
            dueDate: 2023-07-07T12:37:20.672Z,
            _id: new ObjectId("64a807003389777bbfe22f2e")
          }
        ],
        __v: 0
      }
    ]