Inn0vative1
Inn0vative1

Reputation: 2145

NestJS and Mongoose find by reference object Id

I have a Mongo collection of Users and a collection of Addresses. Each address is owned by one user.Here are my schema classes:


export type UserDocument = User & mongoose.Document
@Schema({ timestamps: true })
export class User {
  // @Prop({ type: mongoose.Types.ObjectId })
  _id: string

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

  @Prop({ required: true, unique: true })
  email: string

  @Prop({ select: false })
  password: string

}
export const UserSchema = SchemaFactory.createForClass(User)

export type AddressDocument = Address & Document
@Schema({ timestamps: true })
export class Address {
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: User.name })
  user: User

  @Prop()
  line1: string

  @Prop()
  line2?: string

  @Prop()
  city: string

  @Prop()
  state?: string

  @Prop()
  country: string
}

export const AddressSchema = SchemaFactory.createForClass(Address)

Then I have an AddressService that can fetch addresses for a user:

@Injectable()
export class AddressService {
  constructor(@InjectModel(Address.name) private addressModel: Model<AddressDocument>) {}

  async save(address: AddressDto): Promise<Address> {
    const model = await this.addressModel.create(address)
    return model.save()
  }

  async findAddressForUser(userId: string) {
    const userObjectId = new mongoose.Types.ObjectId(userId)
    const users = await this.addressModel.find({ user: userObjectId })
  }
}

This code has an error: Type '_ObjectId' is not assignable to type 'Condition<User> I tried passing the userId as a string as well, that did not work either.

What is the right way to query a collection using a reference Id from another collection?

Upvotes: 18

Views: 38355

Answers (4)

Osin
Osin

Reputation: 485

In my case, after reviewing all of your answer, i saw that i had already resolved the question by a certain way somewhere in my app.

So basically what work on my side:

@ObjectType()
export class Account implement AccountInterface{
  ...some properties
  //to manage Parent relation
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
  owner: User;
  
  //to manage Children relation
  @Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }])
  users?: [User];
}

My AccountInterface

export interface AccountInterface {
  //some properties
  owner: User;
  users: [Users]
}

seems to work without problem.

const account = await this.accountModel.find({
  owner: user._id,
});

However you need to define the way data should be fetch from parent and children. The answer above from EzPizza appears to be the best way to handle relationship.

Upvotes: 0

Juan Manuel Pipa Haya
Juan Manuel Pipa Haya

Reputation: 21

The following code worked:

async findModulesUser(id: string) {
    const record = await this.modulesuserModel
      .find({ user: new Types.ObjectId(id) })
      .populate("user", ["name", "email"])
      .exec();

    return record;
}

Upvotes: 2

EzPizza
EzPizza

Reputation: 1150

I think the user field shouldn't be of type User, despite what the NestJS docs say. It should be of type Types.ObjectId.

Something like this (with cleaner imports aswell):

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

export type AddressDocument = Address & Document
@Schema({ timestamps: true })
export class Address {
  @Prop({ type: SchemaTypes.ObjectId, ref: User.name })
  user: Types.ObjectId;

  ...

}
export const UserSchema = SchemaFactory.createForClass(User)

I got this from here.

You could also tell TypeScript that this may be either a string, an ObjectId or a User by declaring it as: user: string | Types.ObjectId | UserDocument;

Upvotes: 18

Inn0vative1
Inn0vative1

Reputation: 2145

Not sure if this is the proper way of doing this but this is what I ended up doing to get around that error:

  async findAddressForUser(userId: string): Promise<Address[]> {
    const query: any = { user: new mongoose.Types.ObjectId(userId) }
    return await this.addressModel.find(query).exec()
  }

Upvotes: 8

Related Questions