NestJS 디자인 패턴
Controller
- express에서 router의 역할
- request, resposne, domain, url 등을 제어
Service
DTO : Data Transfer Object
→ 계층간 데이터 교환을 위한 객체
- DB에서 데이터를 얻어 Service나 Controller 등으로 보낼 때 사용하는 객체
- Request와 Response용 DTO는 View를 위한 클래스
회원가입 Service
cats.request.dto.ts 생성
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class CatRequestDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
password: string;
@IsString()
@IsNotEmpty()
name: string;
}
cats.controller.ts
@Post
import { CatRequestDto } from './dto/cats.request.dto';
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
async signUp(@Body() body: CatRequestDto) {
return "signUp Success"
}
}
email 정보를 빼먹고 Post 요청을 보낼 경우 아래와 같이 Validation에 의해
"email should not be empty"
"email must be an email" 메세지가 출력된다.
이제 Service에 데이터를 넘겨주고 서비스에서 로직을 수행하도록 해야한다.
cats.controller.ts
catService 연결
import { CatRequestDto } from './dto/cats.request.dto';
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
async signUp(@Body() body: CatRequestDto) {
return this.catsService.signUp(body);
}
}
cats.service.ts
CatsService에 SignUp 서비스를 생성
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import * as bcrypt from 'bcrypt';
import { Cat } from './cats.schema';
import { CatRequestDto } from './dto/cats.request.dto';
@Injectable()
export class CatsService {
// Dependency Injection
constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) {}
async signUp(body: CatRequestDto) {
const { email, name, password } = body;
const isCatExist = await this.catModel.exists({ email });
// 이메일 중복 검사 및 Exception 처리
if (isCatExist) {
throw new UnauthorizedException(
'해당하는 고양이는 이미 존재하는 고양이입니다.',
);
}
// 비밀번호 암호화
const hashedPassword = await bcrypt.hash(password, 10);
// db에 저장
const cat = await this.catModel.create({
email,
name,
password: hashedPassword,
});
return cat
}
}
+ password 암호화 라이브러리 설치 필요
$ npm i bcrypt
$ npm i -D @types/bcrypt
cats.module.ts
스키마를 사용하기 위해서 cats.module.ts에 import를 해주어야 한다.
@Module({
imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
cats.schema.ts
그러나 현재 return cat을 통해 클라이언트 쪽에 암호화된 비밀번호를 노출시켜서는 안된다.
따라서 스키마 단계에서 비밀번호만 virtual field를 통해 숨긴다.
@Schema(options)
export class Cat extends Document {
...
@Prop({
required: true,
})
@IsString()
@IsNotEmpty()
password: string;
readonly readOnlyData: { id: string; email: string; name: string };
}
export const CatSchema = SchemaFactory.createForClass(Cat);
CatSchema.virtual('readOnlyData').get(function (this: Cat) {
return {
id: this.id,
email: this.email,
name: this.name,
};
});
cats.service.ts
return cat.readOnlyData; 로 반환하게 하면 id, name, email만 반환해주게 된다.
@Injectable()
export class CatsService {
constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) {}
async signUp(body: CatRequestDto) {
const { email, name, password } = body;
...
return cat.readOnlyData;
}
}
+ Mongoose debug가 터미널에서 출력되게 하는 법
app.module.ts
export class AppModule implements NestModule {
private readonly isDev: boolean = process.env.MODE === 'dev' ? true : false;
configure(consumer: MiddlewareConsumer) {
mongoose.set('debug', this.isDev); // mongoose query log
}
}