Search code examples
node.jstypescriptmongoose

Strict Typing for Mongoose Populate


Is there any way to define / amend an interface or type as to assert that a document will be populate in typescript using mongoose?

e.g.

interface ISchool {
   name: string;
   address; string;
}
interface IPerson {
   name: string;
   school?: PopulatedDoc<ISchool & Document>
}
const PersonSchema: Schema<IPerson> = new Schema<IPerson>({
    name: { type: String },
    school: { type: Schema.Types.ObjectId, ref: 'School' }
})
const Person = mongoose.model<IPerson>('Person', PersonSchema);
export default Person

Then essentially if we ever interact with a Person document, there is no way of knowing if the school property is populated of not. eg.

const person = await Person.findOne({});
if (person.school) { . . . } // is `school` an ObjectId or a Document?

and

const person = await Person.findOne({}, undefined, { populate: { path: 'school', model: 'School'} });
if (person.school) { . . . } // is `school` an ObjectId or a Document?

Is there any way to assert that a document property has been populated?

Thanks


Solution

  • From the populate-with-typescript documentation, we can:

    add a generic parameter Paths to the populate()

    import mongoose, { Types } from 'mongoose';
    
    interface ISchool {
        name: string;
        address: string;
    }
    interface IPerson {
        name: string;
        school?: Types.ObjectId;
    }
    const PersonSchema: mongoose.Schema<IPerson> = new mongoose.Schema<IPerson>({
        name: { type: String },
        school: { type: mongoose.Schema.Types.ObjectId, ref: 'School' },
    });
    
    const Person = mongoose.model<IPerson>('Person', PersonSchema);
    
    (async function run() {
        const person1 = await Person.findOne({});
        person1?.school; // school is ObjectId
    
        const person2 = await Person.findOne({}).populate<{ school: ISchool }>({ path: 'school', model: 'School' });
        person2?.school; // scholl is ISchool
    })();