[ NestJS + mongoose ] virtual field에 대하여 알아보자
🌈 프로그래밍/Nest JS

[ NestJS + mongoose ] virtual field에 대하여 알아보자

반응형

 

안녕하세요? 수구리입니다.

정말 오랜만에 또 포스팅을 하게 되었습니다!

이번 포스팅에서는 NestJS와 mongoose에서 virtual field에 대하여 알아보려고 합니다.

 

우선 간단하게 설명하자면,,

예를 들어 어떤 한 유저가 SignUp을 한다고 가정해봅시다.

그러면 이 유저는 ID, PW 등등 자신의 정보를 입력하고 DB에 Save를 하게 됩니다.

Save 이후, 성공적으로 Data를 저장했다면 입력된 정보를 return 해주는 경우가 있다고 해봅시다.

Save 성공 이후, 만약 이 유저의 ID를 비롯한 모든 정보가 다시 return이 된다면 큰일이 나겠죠??

따라서 이러한 문제를 해결하기 위해서 mongoose에서는 virtual field를 제공합니다.

DB의 collection에는 존재하지 않는 가상의 field를 만들어 read만 가능하게 끔 하도록 하는 필드를 말이죠..!

 

바로 아래에는 Nest 공식문서를 참고한 고양이 커뮤니티 프로젝트의 예시입니다.

cats.schema.ts

@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);

위와 같은 Cat이라는 스키마가 있다고 해봅시다. (유저와 비슷하다고 생각하시면 됩니다.)

한 고양이는 email을 가지고 있고, 비밀번호와 자신의 이름 그리고 사진까지 가질 수 있습니다.

 

cats.controller.ts

@ApiOperation({ summary: '회원가입' })
@Post()
async signUp(@Body() body: CatRequestDto) {
    return await this.catsService.signUp(body);
}

위의 부분에서는 /cats 에 대한 컨트롤러 부분 중 signUp과 관련된 컨트롤러입니다.

Dto를 사용해서 Data가 알맞게 들어왔는지 검증하고, 서비스 측으로 입력된 body 부분을 보내는 모습입니다.

여기서 body의 모습을 console.log로 보면..

위와 같습니다. 이 정보가 body에 실려있다는 의미이죠.

아무튼 이제 서비스단으로 이동해보겠습니다.

 

cats.service.ts

@Injectable()
export class CatsService {
	// 의존성 주입
	constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) { }

	async signUp(body: CatRequestDto) {
		const { email, name, password } = body;

		// email 중복 확인
		const isCatExist = await this.catModel.exists({ email });

		if (isCatExist) {
			throw new UnauthorizedException('해당하는 고양이는 이미 존재합니다!');
			//throw new HttpException('해당하는 고양이는 이미 존재합니다!', 403);
		}

		// password 암호화
		const hashedPassword = await bcrypt.hash(password, 10);

		// DB save
		const cat = await this.catModel.create({ email, name, password: hashedPassword, });

		// 모든 필드를 return..?!
		return cat;
	}
}

위의 부분에서는 컨트롤러의 signUp 함수에 대한 구현부분이라고 할 수 있습니다.

실제로 DB에 접근하여 데이터를 저장하는 (cat 데이터를 save) 부분이죠.

하지만, 마지막 부분에서 DB에 save한 뒤, cat을 return 한다고 하면 결과가 다음과 같습니다.

왼쪽이 요청부분이고, 오른쪽이 응답 부분인데 응답 부분에서 password까지 방금 회원 가입한 cat에 대한 모든 정보가 return 되는 모습입니다.

바로 이 부분을 해결하기 위해서 스키마 부분에서 virtual feild를 사용해보겠습니다!

 

수정된 cats.schema.ts

@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: { id: string; email: string; name: string };
}

export const CatSchema = SchemaFactory.createForClass(Cat);

// Schema에 virtual이라는 메서드를 사용해서 필드 name으로 모델을 필요한 데이터만 전달해주기 위함
CatSchema.virtual('readOnlyData').get(function (this: Cat) {
	return {
		id: this.id, // this는 하나의 객체를 의미. 
		email: this.email,
		name: this.name,
	}
})

어떤게 달라진 지 보이시나요? 스키마를 정의하는 부분 가장 아래쪽에 readOnlyData라는 가상의 필드를 만들어주었습니다.

하단에는 virtual이라는 메서드를 사용해서 어떤 정보를 readonly 형태로 return 할지를 정해주는 모습입니다.

이렇게 한 뒤, 방금 서비스도 아래와 같이 수정한다면

 

수정된 cats.service.ts

@Injectable()
export class CatsService {
	// 의존성 주입
	constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) { }

	async signUp(body: CatRequestDto) {
		// 생략
        
		// 모든 필드를 return하는 것이 아니라 필요한 정보만 return
		return cat.readOnlyData;
	}
}

cat의 virtual 속성인 readOnlyData를 return 한다면 결과는 아래와 같습니다.

 

이상으로 NestJS와 mongoose의 virtual feild에 대해서 알아보았습니다. 

감사합니다.

반응형