Mongoose는 Virtual 기능을 통해 가상의 필드를 만들어 프론트에 응답값으로 보내줄 수 있다. 👉 공식문서
쉽게 말해, DB에서 원하는 필드만 골라서, 혹은 원하는 값만 가공하여 내려주는 기능으로 활용할 수 있다는 의미이다.
예를 들어, 아래와 같은 Cat 스키마가 있다고 가정하자. (NestJS,TypeScript,Mongoose,MongoDB, class-validator).
@Schema(options)
export class Cat extends Document {
@Prop({
required: true,
unique: true,
})
@IsEmail()
@IsNotEmpty()
email: string;
@Prop({
required: true,
})
@IsString()
@IsNotEmpty()
name: string;
@Prop({
required: true,
})
@IsString()
@IsNotEmpty()
password: string;
@Prop()
@IsString()
imgUrl: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
상기 스키마는 email, name, password를 필수값으로 갖는다.
회원가입과 같이 'Cat 생성'에 관한 요청이 들어오는 경우를 생각해보자.
단순히 아래와 같이 생성된 값을 return 해 줄 수도 있다.
const createdCat = await this.catModel.create({
email,
name,
password: hashedPassword,
});
return createdCat
{
"success": true,
"data": {
"email": "testEmail0@test.com",
"name": "testName0",
"password": "$2b$10$2zub5lcloMnzUiLbklectOksIiQjvlRUKg24ga5UTriAtYgJEtOui",
"_id": "6516d20eb20f56cb694205f7",
"createdAt": "2023-09-29T13:33:02.661Z",
"updatedAt": "2023-09-29T13:33:02.661Z",
"__v": 0
}
}
하지만 아무리 해싱되었다한들 password를 고스란히 프론트에 응답해주는 것은 바람직 하지 못하다.
이에 Schema에 아래와 같이 Mongoose Virtuals 기능을 사용할 수 있다.
@Schema(options)
export class Cat extends Document {
@Prop({
required: true,
unique: true,
})
@IsEmail()
@IsNotEmpty()
email: string;
@Prop({
required: true,
})
@IsString()
@IsNotEmpty()
name: string;
@Prop({
required: true,
})
@IsString()
@IsNotEmpty()
password: string;
@Prop()
@IsString()
imgUrl: string;
readonly readOnlyData: { //2️⃣
id: string;
email: string;
name: string;
};
}
export const CatSchema = SchemaFactory.createForClass(Cat);
CatSchema.virtual('readOnlyData').get(function (this: Cat) { //1️⃣
return {
id: this.id,
email: this.email,
name: this.name,
};
});
1️⃣ readOnlyData라는 프로퍼티를 만들어 return 해줄 값을 설정하고 있다.
2️⃣ 가상의 readOnlyData 필드를 스키마에도 추가시켜준다.
이후, 아래와 같이 응답값에 해당 필드를 return한다.
const createdCat = await this.catModel.create({
email,
name,
password: hashedPassword,
});
return createdCat.readOnlyData;
다시금 응답값을 한눈에 비교해보자:
[before]
{
"success": true,
"data": {
"email": "testEmail0@test.com",
"name": "testName0",
"password": "$2b$10$2zub5lcloMnzUiLbklectOksIiQjvlRUKg24ga5UTriAtYgJEtOui",
"_id": "6516d20eb20f56cb694205f7",
"createdAt": "2023-09-29T13:33:02.661Z",
"updatedAt": "2023-09-29T13:33:02.661Z",
"__v": 0
}
}
[after]
{
"success": true,
"data": {
"id": "65168c2078037b475711043c",
"email": "testEmail1@test.com",
"name": "testName1"
}
}
예시 외에도 setter를 활용하여 값을 가공할 수도 있다.
✅ 주의할 점은 Mongoose virtuals은 DB에 저장되지 않는다는 점이다. 즉, Mongoose virtuals을 바탕으로 쿼리를 진행할 수 없음을 인지해야한다.