
https://docs.nestjs.com/techniques/mongodb#model-injection
nestjs 공식 문서에서는 Mongoose schema를 작성할 때, 참조하는 스키마 클래스를 타입으로 사용하고 있다. 첫 문장의 for populating이 킥이지만, 사용 예시와 설명이 부족하여 제대로 이해하고 사용하기 힘들었기 때문에, schema 안에 다른 schema의 Id가 들어가는 경우의 예시를 들고자 한다.
아래에서 쓰이는 모든 model.find() 메서드와 관련된 정보는 /^find/i 메서드에서도 동일하다.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
export type OwnerDocument = HydratedDocument<Owner>;
@Schema()
export class Owner {
@Prop({type: String})
name: string;
}
export const OwnerSchema = SchemaFactory.createForClass(Owner);
export type CatDocument = HydratedDocument<Cat>;
@Schema()
export class Cat {
@Prop({type: String})
name: string;
@Prop({type: Number})
age: number;
@Prop({type: String})
breed: string;
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' })
owner: Owner;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
공식문서와 비슷한 예제로 위와 같은 상황에서, 아래와 같은 서비스를 사용해보겠다.
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(@InjectModel(Cat.name) private catModel: Model<Cat>) {}
async find(): Promise<Cat[]> {
console.log(this.catModel.find().exec());
/*[
{
...
owner: new ObejctId("any string");
...
}
]*/
}
async findWithPopulate(): Promise<Cat[]> {
console.log(this.catModel.find().populate('owner').exec());
/*[
{
...
owner: {
_id: new ObjectId('any string'),
name: "any string"
}
...
}
]*/
}
}
find()와 findWithPopulate()에서의 owner 값은 각 주석과 같다.
따라서 populate와 함께 사용한 경우에만 owner의 타입이 Owner로 일치하는 것을 알 수 있다.
즉, Type을 Owner로 한 경우에는 .populate('owner')를 써야만 한다.
만약 그냥 populate 없이 find만 한다면 어떤 문제가 발생할까?
//find 함수 안
const catDocs = awiat this.catModel.find().exec();
const OwnerNames = catDocs.map(e=>e.owner.name); //[undefiend,undefined, ...]
const OwnerIds = catDocs.map(e=>e.owner._id.toString()); //throw Error
이렇게 작성했을 경우, typescript는 e.owner의 타입을 Owner로 인식하고 있기 때문에 .name이나 ._id을 해도 오류를 인지하지 못하고, 빨간줄과 함께 경고가 표시되지 않는다.
이대로 실행할 경우 undefined만을 리턴할것이고, 심이저 e.owner._id.toString()은 Cannot read properties of undefined (reading 'toString') 에러를 보게 될 것이다.
그럼 만약 자신이 populate와 함께 find를 쓰지 않을 계획이라면 어떻게 해야할까?
populate 없이 find만 쓰는 경우, schema에서 owner의 타입은 Owner가 아닌 OjbectId이면 된다.
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' })
owner: mongoose.Tyeps.ObjectId;
이렇게 mongoose.Types.ObjectId를 쓰면 된다. @Prop에 있는 Type과 다르다는 것에 주의해야한다.
monggose의 Schema를 작성하기에 필요한 Type과, 실제로 find시 owner에 들어가는 type은 mongodb에서 리턴해주는 ObejctId의 Type이 다르기 때문인데, 아래 모든 표현은 동일하므로 취향껏 사용하면 된다. (가장 글자수 적은 SchemaTypes와 Types 쓰는 방식 추천)
import { BSON } from 'mongodb';
import { Schema, SchemaTypes, Types } from 'mongoose';
//schema 클래스 내부
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' })
owner: mongoose.Tyeps.ObjectId;
@Prop({ type: Schema.Types.ObjectId, ref: 'Owner' })
owner: Tyeps.ObjectId;
@Prop({ type: SchemaTypes.ObjectId, ref: 'Owner' })
owner: BSON.ObjectId;
import { ObejctId } from 'mongoose';는 Schema.Types.ObjectId의 "타입"(타입스크립트의 타입, type ObjectId = Schema.Types.ObjectId;)만을 가리키므로 @Prop의 type(클래스가 와야함)으로 쓸 수 없고, 당연히 mongodb의 ObejctId가 아니니 owner: 타입 에도 쓸 수 없다이 경우에는 반대로 find 시 populate를 쓰면 타입이 일치하지 않는다. 즉, 참조하는 document를 한번에 가져올 수 없고, find를 2번 해야한다.
owner: Owner를 사용하고, model.find()할때 항상 .populate("owner")를 추가하자.owner:Types.ObjectId를 사용하고, model.find()를 populate없이 사용하면 된다.owner:Owner |Types.ObjectId를 사용하거나 제너릭을 사용하여 class Cat<T extends Types.ObjectId | Owner >, owner: T, HydratedDocument<Cat<Types.ObjectId | User>> 등으로 세팅하고, find 시에는 as Memo<Types.ObjectId>[]를 populate 시에는 as Memo<Owner>[]를 사용하는 방식도 고려해볼 수 있다.https://docs.nestjs.com/techniques/mongodb
https://mongoosejs.com/docs/populate.html
이 글을 작성하기 전 찾아보았던 질문인데, 답변에서는 Types.ObjectId만 맞다고 한다. Populate를 쓰는 경우를 고려하지 않았기에 반쪽짜리 답변이라 생각하지만, 덕분에 Types.ObjectId를 사용하는 방법을 알게되었기에 링크를 남긴다.
https://stackoverflow.com/questions/66395079/nestjs-and-mongoose-find-by-reference-object-id
타입이라는 말을 클래스로도 쓰는게 헷갈리게 하는 포인트인것 같아요.
잘 읽고 갑니다~