2022. 12. 29. 13:52γBackend/NestJS
μ΄μ κΈ
- 2022.12.22 - [Backend/NestJS] - NestJS Request Lifecycle (0) - κ°μ
- 2022.12.22 - [Backend/NestJS] - NestJS Request Lifecycle (1) - Middleware
- 2022.12.23 - [Backend/NestJS] - NestJS Request Lifecycle (2) - Guard
- 2022.12.26 - [Backend/NestJS] - NestJS Request Lifecycle (3) - Interceptor
νμ΄ν
NestJSμμ νμ΄νλ 컨νΈλ‘€λ¬μ μλ¨μμ μ¬μ©μμ μμ²μ λν΄ λ€μμ μν μ μννλ μλͺ μ£ΌκΈ°λ₯Ό μλ―Ένλ€.
- λ³ν: μ¬μ©μμ μμ² μ λ°μ΄ν°λ₯Ό μνλ ννλ‘ λ³κ²½ ν 컨νΈλ‘€λ¬μ λ겨μ€λ€.
- κ²μ¦: μ¬μ©μμ μμ² μ λ°μ΄ν°μ λν΄, 컨νΈλ‘€λ¬ λ¨μ λλ¬νκΈ° μ κ²μ¦μ μννκ³ κ²μ¦μ΄ μ€ν¨ν μ μ΄λ₯Ό 컨νΈλ‘€λ¬ λ¨μ λκΈ°μ§ μκ³ λ°λ‘ μ¬μ©μμκ² μλ¬λ₯Ό λ°ννλ€.
νμ΄νμ μ‘΄μ¬ μ΄μ ?
NestJSμ μν€ν μ³μμ νμ΄νκ° λ°λ‘ μ‘΄μ¬νλ μ΄μ λ λ°λ‘ κ΄μ¬μ¬ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν¨μ΄λ€. νμ΄νμμ μννλ λ‘μ§μ 컨νΈλ‘€λ¬ λ΄μ μμΉν΄λ λμ§λ§, κ·Έλ΄ κ²½μ° μ»¨νΈλ‘€λ¬μ κ΄μ¬μ¬κ° λͺ¨νΈν΄μ§ μ μμΌλ©° λΉμ·ν λ°μ΄ν°μ κ²μ¦μ λν΄ μ¬μ¬μ©μ±λ λλΉ μ§λ€. λ°λΌμ NestJSμμλ λ©μΈ λΉμ¦λμ€ λ‘μ§μ μ€μΌμ λ°©μ§νκ³ μ λ°μ΄ν° κ²μ¦ κ΄λ ¨ λ‘μ§μ νμ΄νμ λΆλ¦¬νμ¬ μμ±νλ€.
νμ΄ν μμ±
NestJSμμλ κΈ°λ³Έμ μΌλ‘ μλ 9μ’ μ κΈ°λ³Έμ μΌλ‘ μ 곡λλ νμ΄νλΌμΈμ΄ μ‘΄μ¬νκ³ , μ΄λ₯Ό νμ©νλ©΄ λλΆλΆμ λ³ν, κ²μ¦ λ‘μ§μ ꡬνν μ μλ€. (μλ νμ΄νλ€μ μΈλΆμ μΈ μ 보λ 곡μλ¬Έμ μ°Έμ‘°)
- ValidationPipe
- ParseIntPipe
- ParseFloatPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- ParseEnumPipe
- DefaultValuePipe
- ParseFilePipe
λ€λ§, κΈ°λ³Έμ μΌλ‘ μ 곡λλ νμ΄νλ‘λ μλΉμ€ λ‘μ§μ ꡬννκΈ° λΆμ‘±νλ€λ©΄ μ§μ 컀μ€ν νμ΄νλ₯Ό μμ±ν΄μ νμ©ν μλ μλ€.
컀μ€ν νμ΄ν μμ±
컀μ€ν νμ΄νλ₯Ό μμ±νκΈ° μν΄μλ NestJSμ PipeTransformμ μμν ν΄λμ€μ ν΄λμ€ λ΄ transform λ©μλλ₯Ό ꡬνν΄μΌ νλ€.
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value; // λͺ¨λ valueλ₯Ό ν΅κ³Όμν€λ νμ΄ν
}
}
μ μμλ 곡μλ¬Έμμ λͺ¨λ κ°μ ν΅κ³Όμν€λ νμ΄ν μμμ΄λ€. transform λ©μλλ 2κ°μ μΈμλ₯Ό κ°λλ€. 첫λ²μ§Έ μΈμμΈ valueλ 컨νΈλ‘€λ¬μμ μ²λ¦¬λλ μΈμμ΄κ³ (λ€λ§ 컨νΈλ‘€λ¬μ λλ¬νμ§λ λͺ»ν μν), λλ²μ§Έ μΈμμΈ metadataλ μμ valueμ λ©νλ°μ΄ν°μ΄λ€.
컀μ€ν νμ΄νμμμ μμΈ μ²λ¦¬
νμ΄ν λ΄μμ λ°μνλ κ°μ’ μλ¬λ€μ μ λΆ NestJSμ νν° λ μ΄μ΄ λ΄μμ λ°μνλ€. λ°λΌμ νμ΄ν λ΄μμ κ²μ¦ λ‘μ§μ΄ μ€ν¨νμ¬ κ·Έμ λ°λΌ μλ¬λ₯Ό λ°νν΄λ NestJSμ νν°κ° μ μλνλ€. κ²°κ³Όμ μΌλ‘ κ²μ¦μ΄ μ€ν¨νμ λμ μμΈ μ²λ¦¬λ νν° λ‘μ§μ μμνκ³ , νμ΄ν λ¨μμλ μλ¬ μν©μ λ§λ 컀μ€ν μλ¬λ₯Ό λμ§κΈ°λ§ νλ©΄ λλ€.
NestJSμμ κΈ°λ³Έμ μΌλ‘ μ 곡λλ νμ΄νκ° κ°λ ₯νκ³ , 컀μ€ν νμ΄νλ₯Ό μμ±νλ λ°©λ²μ 곡μλ¬Έμμ μ λμμκΈ° λλ¬Έμ μ΄μ λν΄μλ λ€μμ λ§ν¬λ₯Ό μ°Έκ³ λ°λλ€.
https://docs.nestjs.com/pipes#built-in-pipes
νμ΄ν μ μ©
NestJSμμ νμ΄νλ₯Ό μ μ©νλ λ°©μμ ν¬κ² 4κ°μ§κ° μ‘΄μ¬νλ€.
- λͺ¨λ λ°μ μμΉνλ μ μ νμ΄ν
- λͺ¨λ λ΄μ μμΉνλ μ μ νμ΄ν
- 컨νΈλ‘€λ¬ λ©μλ(νΈλ€λ¬) λ 벨μ μμ©νλ νμ΄ν
- λ©μλ νλΌλ―Έν°(μΈμ) λ 벨μ μμ©νλ νμ΄ν
λͺ¨λ λ°μ μμΉνλ μ μ νμ΄ν
ν΄λΉ λ°©μμ μν΄μ NestJS μ΄ν리μΌμ΄μ μ useGlobalPipes() λ©μλλ₯Ό μ΄μ©ν μ μλ€. μ΄ λ°©μμ λͺ¨λ λ°μ νμ΄νλ₯Ό μμΉμν€κΈ° λλ¬Έμ λ€λ₯Έ λͺ¨λμ κ°μ Έμ μμ‘΄μ±μ μΆκ°νμ¬ μ¬μ©νκ±°λ, νΉμ λ°λμ κ²½μ°κ° λΆκ°λ₯νλ€. μ½λλ λ€μκ³Ό κ°μ΄ μμ±νλ€.
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// μ μ νμ΄ν μ€μ , κΈ°λ³Έ μ 곡 νμ΄νλ₯Ό νμ©
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
ν΄λΉ λ°©μμ μ΄μ©νκΈ° μν΄μλ νμ΄ν ν΄λμ€μ μΈμ€ν΄μ€λ₯Ό μ§μ μΈμλ‘ μΆκ°ν΄μ£Όμ΄μΌ νλ€.
λͺ¨λ λ΄μ μμΉνλ μ μ νμ΄ν
ν΄λΉ λ°©μμ ν΅νλ©΄ νμ΄ν λ΄μμ λ€λ₯Έ λͺ¨λμ μμ‘΄μ±μ μ£Όμ νμ¬ μ¬μ©ν μ μλ€. ν΄λΉ λ°©μμ λ©μΈ λͺ¨λκ³Ό μ°κ²°λ μ΄λ€ λͺ¨λμμ μ£Όμ μ΄ λλλΌλ μ μμΌλ‘ λμΌνκ² μ¬μ©ν μ μκΈ° λλ¬Έμ, 곡μλ¬Έμμμλ νμ΄νκ° μμ±λ λͺ¨λμμ λ°λ‘ μ£Όμ ν κ²μ μΆμ²νκ³ μλ€.
import { Module, ValidationPipe } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
컨νΈλ‘€λ¬ λ©μλ(νΈλ€λ¬) λ 벨μμ μμ©νλ νμ΄ν
ν΄λΉ λ°©μμ NestJSμ @UsePipes() λ°μ½λ μ΄ν°λ₯Ό νμ©νλ λ°©μμ΄λ€. μ΄λ₯Ό ν΅νλ©΄ νΉμ νΈλ€λ¬μ λ€μ΄μ€λ λ°μ΄ν°μ νμ΄νλ₯Ό μ μ©ν μ μλ€. ν΄λΉ λ°©μμ λ°μ½λ μ΄ν°μ μ μ©ν νμ΄ν ν΄λμ€λ₯Ό λ£κ±°λ νΉμ μΈμ€ν΄μ€λ₯Ό λ£λ λ°©μμΌλ‘ μ μ©μ΄ κ°λ₯νλ€. λ§μ½ ν΄λμ€μ μμ±μμ μΈμλ₯Ό ν΅ν΄ νμ΄νμ μν μ μ μν΄μΌ νλ€λ©΄, μΈμ€ν΄μ€ λ°©μμ νμ©ν μ μλ€.
import {
Body,
Controller,
Post,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { SignupDto } from '@auth/dto/signup.dto';
import { AuthService } from '@auth/auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signup')
@UsePipes(ValidationPipe) // λΌμ°ν° νΈλ€λ¬μ νμ΄ν μ μ©
async signup(@Body() signupDto: SignupDto) {
const signupResult = await this.authService.signup(signupDto);
return signupResult;
}
}
νΈλ€λ¬ νλΌλ―Έν°(μΈμ) λ 벨μμ μμ©νλ νμ΄ν
ν΄λΉ λ°©μμ νΈλ€λ¬μ νΉμ νλΌλ―Έν°μ λν΄μλ§ νμ΄νλ₯Ό μ μ©νλ λ°©μμ΄λ€. NestJSμμ μ 곡νλ κ°μ’ νλΌλ―Έν° λ°μ½λ μ΄ν° (ex. @Body(), @Param(), @Query())λ νΉμ NestJSμ customParamDecorator() ν¨μλ₯Ό ν΅ν΄ λ§λ€μ΄μ§ 컀μ€ν λ°μ½λ μ΄ν°μ λν΄ μ μ©λλ€. (μ΄ λ, customParamDecoratorμ NestJSμ ValidationPipeλ₯Ό μ μ©νκΈ° μν΄μλ μ€μ μΌλ‘ λ€μκ³Ό κ°μ΄ μμ±ν΄μ£Όμ΄μΌ νλ€.)
@CustomParam(new ValidationPipe({ validateCustomDecorators: true }))
κΈ°μ‘΄μ μ‘΄μ¬νλ νλΌλ―Έν° λ°μ½λ μ΄ν°λ₯Ό μ¬μ©νλ κ²½μ°μλ λ€μκ³Ό κ°μ΄ μ½λλ₯Ό μμ±ν΄μ£Όλ©΄ λλ€.
import {
Body,
Controller,
Post,
ValidationPipe,
} from '@nestjs/common';
import { SignupDto } from '@auth/dto/signup.dto';
import { AuthService } from '@auth/auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signup')
async signup(@Body(ValidationPipe) signupDto: SignupDto) {
const signupResult = await this.authService.signup(signupDto);
return signupResult;
}
}
DTO λμ (Data Transfer Object)
NestJS 곡μλ¬Έμμμλ νμ΄νμ νμ©μ DTOλ₯Ό μ μ©νλ λ°©μμ μ€λͺ νκ³ μλ€.
μ¬κΈ°μμ DTOλ Data Transfer Objectμ μ½μλ‘ ν΄λΌμ΄μΈνΈμ μλ² κ°, νΉμ μλ² λ΄ κ° λ μ΄μ΄ κ° λ°μ΄ν° ν΅μ μ λ°μ΄ν°μ ꡬ쑰λ₯Ό κ°μ νλ μν μ νλ€. DTOλ νμ μ€ν¬λ¦½νΈ μΈν°νμ΄μ€κ° μλ ν΄λμ€λ‘ μ μΈλμ΄μΌ νλλ°, κ·Έ μ΄μ λ λ°νμμμλ κ³μ μλμ ν΄μΌνκΈ° λλ¬Έμ΄λ€. (μΈν°νμ΄μ€λ‘ μ μΈ μ μ€ν μ νμ 체νΉλ§μ λμμ€ λΏ λ°νμμλ μν₯μ λ―ΈμΉμ§ λͺ»νλ€.)
μ΄λ₯Ό ν΅νλ©΄ νμ΄νλ₯Ό μμ½κ² ꡬνν μ μλ€.
λ€μκ³Ό κ°μ΄ DTOλ₯Ό μ μΈν μ μλ€.
// signup.dto.ts
import { IsEmail, IsString } from 'class-validator';
export class SignupDto {
@IsEmail() // email κ²μ¦μ μν λ°μ½λ μ΄ν°
readonly email: string;
@IsString() // λ¬Έμμ΄ κ²μ¦μ μν λ°μ½λ μ΄ν°
readonly password: string;
}
μ΄λ₯Ό νΉμ 컨νΈλ‘€λ¬μ λ©μλμ μ μ©νμ¬ νμ΄νλ₯Ό ꡬννκΈ° μν΄μλ λ€μκ³Ό κ°μ΄ μμ±ν μ μλ€.
import {
Body,
Controller,
Post,
ValidationPipe
} from '@nestjs/common';
import { SignupDto } from '@auth/dto/signup.dto';
import { AuthService } from '@auth/auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signup')
@UsePipes(ValidationPipe)
async signup(@Body() signupDto: SignupDto) {
const signupResult = await this.authService.signup(signupDto);
return signupResult;
}
}
μμ κ°μ΄ μμ±νλ©΄ SignupDtoμμ μμ±ν ꡬ쑰λλ‘ νμ΄ν λ¨μμ κ²μ¦μ΄ μ΄λ£¨μ΄μ§λ€.
λ§μ½ DTOλ₯Ό μ΄μ©νμ¬ λ°μ΄ν°μ λ³νλ μννκ³ μΆλ€λ©΄ μλμ κ°μ΄ μμ±ν μ μλ€. μλμ μμλ λΉλ°λ²νΈλ₯Ό μ«μνμΌλ‘ λ³ννμ¬ μ λ ₯ λ°μ λ μ΄μ©ν μ μλ€.
import { Transform } from 'class-transformer';
import { IsEmail, IsNumber } from 'class-validator';
export class SignupDto {
@IsEmail()
readonly email: string;
@Transform(({ value }) => Number(value)) // numberνμΌλ‘ λ³ν μν
@IsNumber()
readonly password: number;
}
μ΄ λ μ΄λ₯Ό NestJSμ ValidationPipeλ₯Ό ν΅ν΄ μ μ©νκΈ° μν΄μλ λ€μμ μ€μ μ μΆκ°ν΄μ£Όμ΄μΌ νλ€.
// ν΄λμ€κ° μλ μΈμ€ν΄μ€λ‘ μ€μ μ μΆκ°νμ¬ νμ΄ν μ€μ μ ν΄μ£Όμ΄μΌ ν¨
// transform : true
// ValidationPipeμμ λ³ν μμ
κΉμ§ μνν΄μ€(νλ³ν, κΈ°λ³Έκ° μ€μ )
new ValidationPipe({transform: true})
'Backend > NestJS' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
NestJS Request Lifecycle (5) - Exception Filter (0) | 2023.01.13 |
---|---|
NestJS Request Lifecycle (3) - Interceptor (0) | 2022.12.26 |
NestJS Request Lifecycle (2) - Guard (0) | 2022.12.23 |
NestJS Request Lifecycle (1) - Middleware (0) | 2022.12.22 |
NestJS Request Lifecycle (0) - κ°μ (0) | 2022.12.22 |