How to populate mongoose references in nestjs?
Asked Answered
A

4

16

I define a Person and Story schemas :

    @Schema()
    export class Person extends Document {
      @Prop()
      name: string;
    }
    export const PersonSchema = SchemaFactory.createForClass(Person);
    
    
    @Schema()
    export class Story extends Document {
    
      @Prop()
      title: string;
    
      @Prop()
      author:  { type: MongooseSchema.Types.ObjectId , ref: 'Person' }
    
    }
    export const StorySchema = SchemaFactory.createForClass(Story);

In my service I implemented save and read functions:

        async saveStory(){
        const newPerson = new this.personModel();
        newPerson.name  = 'Ian Fleming';
        await newPerson.save();
        const newStory  = new this.storyModel();
        newStory.title = 'Casino Royale';
        newStory.author = newPerson._id;
        await newStory.save();
      }
    
      async readStory(){
        const stories = await this.storyModel.
            findOne({ title: 'Casino Royale' })
        console.log('stories ',stories);
      }

When I ran readStory() I get the following output:

     stories  {
      _id: 5f135150e46fa5256a3a1339,
      title: 'Casino Royale',
      author: 5f135150e46fa5256a3a1338,
      __v: 0
    }

When I add a populate('author') to my query then I get author as null:

     stories  {
      _id: 5f135150e46fa5256a3a1339,
      title: 'Casino Royale',
      author: null,
      __v: 0
    }

How do I populate the author field with the referenced Person document ?

Aeolis answered 18/7, 2020 at 19:49 Comment(0)
B
18

After much reading and testing on mongoose references in nestjs. I think the accepted answer can be improved. I will show this in 2 steps. The first step is showing the declaration of MongooseSchema and including the comment of @illnr regarding the author property to use Types.ObjectId instead of MongooseSchema.Types.ObjectId.

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types, Schema as MongooseSchema } from 'mongoose';

@Schema()
export class Story extends Document {

  @Prop()
  title: string;

  @Prop({ type: MongooseSchema.Types.ObjectId , ref: 'Person' })
  author:  Types.ObjectId 

}

export const StorySchema = SchemaFactory.createForClass(Story);

And as a second step, I think it improves readability to use the Person class as type for the author property, as shown here.

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types, Schema as MongooseSchema } from 'mongoose';
import { Person } from './person.schema'

@Schema()
export class Story extends Document {

  @Prop()
  title: string;

  @Prop({ type: MongooseSchema.Types.ObjectId , ref: 'Person' })
  author:  Person

}

export const StorySchema = SchemaFactory.createForClass(Story);
Brethren answered 4/1, 2021 at 9:2 Comment(3)
By changing author to a person, upon creating mongo throws a cast error.Opinionated
I feel like this should be in the official NestJS Mongoose docs. After implementing this change, I removed the circular references NestJS was throwing.Unriddle
The only issue with this is that when you populate the sub Document and try to use it, there will be a type error because it expects it to be an ObjectId. I'm currently trying to figure out how to fix this.Unriddle
A
8

Found it. My mistake was in defining the schema. Should be :

@Schema()
export class Story extends Document {
  @Prop()
  title: string;
    
  @Prop({ type: MongooseSchema.Types.ObjectId , ref: 'Person' })
  author:  MongooseSchema.Types.ObjectId  
}
Aeolis answered 18/7, 2020 at 20:48 Comment(3)
Thank you! Setting the type in the Prop options was the solution to my problem, since I am using union types for actual typescript typings. Btw: you can also import { Types } from 'mongoose'; and then use author: Types.ObjectId.Nessus
How would you write this for an array? I tried wrapping in an array like this: @Prop([{ type: MongooseSchema.Types.ObjectId , ref: 'Person' }])Livengood
@Livengood I am not sure you can have an array of references that can be populated automatically. But I don't know.Aeolis
I
4

None of above work for me, I had to use populate(). Reference from https://dev.to/mossnana/nestjs-with-mongoose-populate-4mo7?signin=true

An example of complete code and structure users.service.ts

import { User, UserDocument } from 'src/schemas/user.schema';
import { Role, RoleDocument } from 'src/schemas/role.schema';

...

constructor(
    @InjectModel(User.name) private userModel: Model<UserDocument>,
    @InjectModel(Role.name) private roleModel: Model<RoleDocument>,
    private roleService: RolesService
  ) {}


async findOne(id: string) {
    return await this.userModel.findOne({ _id: id }).populate('role', '', this.roleModel).exec();
}

user.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Role } from './role.schema';

export type UserDocument = User & mongoose.Document;

@Schema()
export class User {
  @Prop({ required: true, type: String })
  email: string;

  @Prop({ type: String })
  name: string;

  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Role' })
  role: Role;
}

export const UserSchema = SchemaFactory.createForClass(User);

role.schema.ts

export type RoleDocument = Role & mongoose.Document;

@Schema()
export class Role {
  @Prop({ type: String, required: true, unique: true, index: true })
  name: string;

  @Prop({ type: [String], required: true })
  permissions: string[];
}

export const RoleSchema = SchemaFactory.createForClass(Role);
Inseverable answered 1/10, 2021 at 6:46 Comment(0)
G
0

In my response, I defined booking and tasker schemas. I used a UUID (which generates a unique string) instead of an ObjectID to override the default ObjectID of each MongoDB document. Due to the taskerID property, I used the type as string instead of Types.ObjectId.

booking.schema.ts

import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import mongoose, { HydratedDocument } from "mongoose";

export type BookingDocument = HydratedDocument<Booking>;

@Schema({ collection: "booking" })
export class Booking {
  @Prop({ required: true })
  _id: string;

  @Prop({required: true, type: mongoose.Schema.Types.String, ref: "Tasker"})
  taskerID: string;
}

export const BookingSchema = SchemaFactory.createForClass(Booking);

export const BookingModel = {name: Booking.name, schema: BookingSchema};

tasker.schema.ts

import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument } from "mongoose";

export type TaskerDocument = HydratedDocument<Tasker>;

@Schema({ collection: "tasker" })
export class Tasker {
  @Prop({ required: true })
  _id: string;

  @Prop({ required: true })
  phone: string;

  @Prop({ required: true })
  name: string;
}

export const TaskerSchema = SchemaFactory.createForClass(Tasker);

export const TaskerModel = { name: Tasker.name, schema: TaskerSchema };

I populated mongoose references as in the below example.

booking.service.ts

import { Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Booking, BookingDocument } from "../db-module/schemas/booking.schema";
import * as mongoose from "mongoose";
import { Tasker, TaskerDocument } from "../db-module/schemas/tasker-user.schema";

@Injectable()
export class BookingService {
  constructor(
    @InjectModel(Booking.name)
    private bookingModel: mongoose.Model<BookingDocument>,
    @InjectModel(Tasker.name)
    private taskerModel: mongoose.Model<TaskerDocument>
  ) {}

  async findById(id: string): Promise<Booking> {
    const Booking = await this.bookingModel.findOne({ _id: id }).populate(
      "taskerID",
      "",
      this.taskerModel,
    ).exec();
    return Booking.toJSON();
  }
}
Gummosis answered 20/4 at 13:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.