일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- spring boot
- 코틀린
- restful api
- Producer
- 자바
- 상속
- java
- 조건문
- 개발이 취미인 사람
- SWIFT
- vue
- file upload
- Sequelize
- 반복문
- front-end
- Kotlin
- state
- class
- Nest.js
- swagger
- react
- node.js
- back-end
- It
- component
- kafka
- AWS
- 개발자
- javascript
- props
- Today
- Total
개발이 취미인 사람
[Nest.js] Nest.js API 만들기 (3) - DTO(Data Transfer Object) & 유효성 검사(Validation Check) & Pipes 본문
[Nest.js] Nest.js API 만들기 (3) - DTO(Data Transfer Object) & 유효성 검사(Validation Check) & Pipes
RyanSin 2021. 11. 10. 21:17- 개요
안녕하세요. 이번 시간에는 DTO와 유효성 검사에 대해 알아보겠습니다.
지난 시간에 Nest.js Handler에 대해 알아봤습니다. 혹시 놓치고 오신 분들은 아래 링크를 통해 학습하고 오시는 걸 추천드리겠습니다.
[Nest.js] Nest.js API 만들기 (2) - Handler(@Get, @Post ...)
- 개념
1. DTO (Data Transfer Object)
DTO(Data Transfer Object)란 무엇일까요? 단어 그대로 "데이터 옮기다 객체로"라고 단어 하나하나씩 해석할 수 있습니다.
즉, 클라이언트에서 서버로 데이터를 전송할 때 사용되며(Cliant -> Server), 계층(@Controller -> @Service) 간에 데이터를 전송할 때도 사용됩니다.
또한 제일 중요한 클라이언트로부터 전송받은 데이터를 객체로 변환할 때도 사용됩니다.
왜 굳이... 객체로 변환해야 할까요??... 하나씩 받아도 되고 크게 문제가 되지 않을 수 있다고 생각을 할 수도 있습니다.
클라이언트에서 전송하는 객체는 기본적으로 타입을 보장하지 않습니다. (Nest.js에서 네트워크 통신을 통해 받은 값은 JavaScript 객체)
그렇기 때문에 우리는 @Controller에서 데이터를 받기 전에 타입 검사와 유효성 검사를 통해 문제를 예방하는 것이 좋습니다.
예전 Express를 사용해서 개발을 하셨던 분들은 특정 라이브러리 또는 유효성 검사와 타입을 체크하는 로직을 실제로 구현하셨을 꺼라 생각이 듭니다.
하지만 Nest.js 프레임워크는 이러한 기능을 지원하기 때문에 개발 시 좀 더 간편하게 대응할 수 있습니다.
2. 유효성 검사 (Validation Check)
유효성 검사(Validation Check)는 크게 설명할 부분이 없다고 생각합니다. (개발자라면... 고된 일이기 때문이죠...)
- 기본 설정
@Controller에서 클라이언트로 객체를 전송받을 때 우리는 @Body 데이코레이터를 통해 값을 전달받았습니다.
하지만 어떠한 값이 어떤 타입을 가지고 전달받는지 우리는 알 수 없습니다.
그렇기 때문에 만약 DTO를 사용하지 않는다면 개발자가 @Body 데코레이터를 하나씩 모두 선언하는 방법을 선택해서 개발하게 됩니다.(노동에 소리가 느껴지네요...)
DTO 파일 생성
user 폴더 하위에 dto 폴더를 생성합니다. user.dto.ts 파일을 통해 우리가 사용할 DTO를 만들어줍니다.
user.dto.ts
/**
* @description SRP를 위반하는 구조이지만 테스트용으로 한 파일에 두 클래스를 선언했다.
*
* SRP란: 한 클래스는 하나의 책임만 가져야한다. (단일 책임의 원칙)
*/
export class CreateUserDto {
id: number; // 유저 고유 아이디
name: string; // 유저 이름
}
export class UpdateUserDto {}
DTO는 우리가 전송받을 클래스 객체를 선언합니다. 유저를 생성하는 부분과 수정하는 부분으로 나눠서 클래스를 만들었습니다.
위와 같이 DTO를 선언해서 사용하면 전송 데이터 형식을 알 수 있습니다. (다른 개발자분들과 협업을 할 때 코드를 확인)
validation 모듈 설치
#npm
npm i class-validator class-transformer
#yarn
yarn add class-validator class-transformer
두 모듈을 통해 우리는 DTO 유효성 검사를 진행할 수 있습니다.
user.dto.ts 파일 수정
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
import { PartialType } from '@nestjs/mapped-types';
/**
* @description SRP를 위반하는 구조이지만 테스트용으로 한 파일에 두 클래스를 선언했다.
*
* SRP란: 한 클래스는 하나의 책임만 가져야한다. (단일 책임의 원칙)
*/
export class CreateUserDto {
@IsNumber()
@IsNotEmpty()
id: number; // 유저 고유 아이디
@IsString()
@IsNotEmpty()
name: string; // 유저 이름
}
export class UpdateUserDto extends PartialType(CreateUserDto) {}
class-validator 모듈을 통해 클라이언트로부터 전송받은 객체 속성을 검사합니다.
- @IsNumber() -> 자료형은 정수형
- @IsNotEmpty() -> 전송된 데이터 값이 null 또는 undefined 라면 에러 발생
UpdateUserDto 속성은 CreateUserDto 클래스와 다르지 않기 때문에 해당 속성을 물려받습니다. (물려받기 위해 PartialType() 사용)
- Pipes 적용하기
위와 같이 기본 설정을 완료했다면 Nset.js에서 지원하는 Pipes를 사용해서 유효성 검사를 진행해보겠습니다.
Pipes란 무엇일까요? Express에서 미들웨어를 사용해 보신 분들이라면 이해가 금방 가실 겁니다.
클라이언트 요청이 @Controller에게 바로 전달되지 않고 Pipes를 한번 거친 다고 생각하시면 좋습니다.
Pipes 사용법
사용방법은 3가지 방법이 있습니다.
- Global-Level
- Handler-Level
- Parameter-Level
내장 파이프 #
Nest는 즉시 사용할 수 있는 8개의 파이프와 함께 제공됩니다.
- ValidationPipe
- ParseIntPipe
- ParseFloatPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- ParseEnumPipe
- DefaultValuePipe
@nestjs/common패키지에서 내 보냅니다. (출처 : 공식 홈페이지)
1. Global-Level Pipe
Global Pipe는 애플리케이션 전체에 적용되는 Pipe입니다. main.ts 파일에 설정합니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
/**
* whitelist: DTO에 없은 속성은 무조건 거른다.
* forbidNonWhitelisted: 전달하는 요청 값 중에 정의 되지 않은 값이 있으면 Error를 발생합니다.
* transform: 네트워크를 통해 들어오는 데이터는 일반 JavaScript 객체입니다.
* 객체를 자동으로 DTO로 변환을 원하면 transform 값을 true로 설정한다.
* disableErrorMessages: Error가 발생 했을 때 Error Message를 표시 여부 설정(true: 표시하지 않음, false: 표시함)
* 배포 환경에서는 true로 설정하는 걸 추천합니다.
*/
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
disableErrorMessages: true,
}),
);
await app.listen(3000);
}
bootstrap();
옵션
Nest.js는 여러 가지 옵션을 지원합니다. 저는 제가 사용했던 옵션만 포스팅하겠습니다.
- whitelist 옵션
해당 옵션은 사용자가 전송한 데이터 중에 DTO 속성으로 정의되지 않은 속성은 제외시키는 옵션입니다.
(해당 옵션 값만 true로 설정 후 테스트 진행)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
/**
* whitelist: DTO에 없은 속성은 무조건 거른다.
* forbidNonWhitelisted: 전달하는 요청 값 중에 정의 되지 않은 값이 있으면 Error를 발생합니다.
* transform: 네트워크를 통해 들어오는 데이터는 일반 JavaScript 객체입니다.
* 객체를 자동으로 DTO로 변환을 원하면 transform 값을 true로 설정한다.
* disableErrorMessages: Error가 발생 했을 때 Error Message를 표시 여부 설정(true: 표시하지 않음, false: 표시함)
* 배포 환경에서는 true로 설정하는 걸 추천합니다.
*/
whitelist: true,
// forbidNonWhitelisted: true,
// transform: true,
// disableErrorMessages: true,
}),
);
await app.listen(3000);
}
bootstrap();
실제 콘솔 로그를 통해 데이터를 확인하면 age 속성 값이 제외된 걸 알 수 있습니다.
- forbidNonWhitelisted 옵션
클라이언트가 전달하는 요청 값 중에 정의되지 않은 속성 값이 있다면 Error를 발생합니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
/**
* whitelist: DTO에 없은 속성은 무조건 거른다.
* forbidNonWhitelisted: 전달하는 요청 값 중에 정의 되지 않은 값이 있으면 Error를 발생합니다.
* transform: 네트워크를 통해 들어오는 데이터는 일반 JavaScript 객체입니다.
* 객체를 자동으로 DTO로 변환을 원하면 transform 값을 true로 설정한다.
* disableErrorMessages: Error가 발생 했을 때 Error Message를 표시 여부 설정(true: 표시하지 않음, false: 표시함)
* 배포 환경에서는 true로 설정하는 걸 추천합니다.
*/
whitelist: true,
forbidNonWhitelisted: true,
// transform: true,
// disableErrorMessages: true,
}),
);
await app.listen(3000);
}
bootstrap();
age 속성은 정의되어 있지 않아 Error가 발생하는 걸 확인할 수 있습니다.
- transfrom 옵션
클라이언트가 전달하는 데이터는 기본적으로 JavaScript 객체입니다. 객체를 자동으로 DTO 객체로 변환할 때 사용합니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
/**
* whitelist: DTO에 없은 속성은 무조건 거른다.
* forbidNonWhitelisted: 전달하는 요청 값 중에 정의 되지 않은 값이 있으면 Error를 발생합니다.
* transform: 네트워크를 통해 들어오는 데이터는 일반 JavaScript 객체입니다.
* 객체를 자동으로 DTO로 변환을 원하면 transform 값을 true로 설정한다.
* disableErrorMessages: Error가 발생 했을 때 Error Message를 표시 여부 설정(true: 표시하지 않음, false: 표시함)
* 배포 환경에서는 true로 설정하는 걸 추천합니다.
*/
whitelist: true,
// forbidNonWhitelisted: true,
transform: true,
// disableErrorMessages: true,
}),
);
await app.listen(3000);
}
bootstrap();
- transform 옵션 false
- transform 옵션 true
옵션을 활성화했기 때문에 전달받은 데이터가 DTO로 변환된 걸 확인할 수 있습니다.
- disableErrorMessages 옵션
서버에서 Error가 발생했을 때 Error 메시지를 노출한다는 건 공격자에게 많은 정보를 제공할 수 있습니다.
그렇기 때문에 배포 단계에는 해당 옵션을 false로 설정해서 배포 환경에서는 노출하지 않는 걸 추천합니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
/**
* whitelist: DTO에 없은 속성은 무조건 거른다.
* forbidNonWhitelisted: 전달하는 요청 값 중에 정의 되지 않은 값이 있으면 Error를 발생합니다.
* transform: 네트워크를 통해 들어오는 데이터는 일반 JavaScript 객체입니다.
* 객체를 자동으로 DTO로 변환을 원하면 transform 값을 true로 설정한다.
* disableErrorMessages: Error가 발생 했을 때 Error Message를 표시 여부 설정(true: 표시하지 않음, false: 표시함)
* 배포 환경에서는 true로 설정하는 걸 추천합니다.
*/
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
disableErrorMessages: true,
}),
);
await app.listen(3000);
}
bootstrap();
2. Handler-level Pipes
핸들러 레벨 파이프는 @Controller 계층에서 @UsePipes() 데코레이터를 선언해 사용합니다.
(main.ts 파일 안에 Global Pipes 설정은 주석 처리합니다.)
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Put,
Query,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
/**
* @author Ryan
* @description @Body 방식 - @Body 어노테이션 여러개를 통해 요청 객체를 접근할 수 있습니다.
*
* CreateUserDto를 사용해서 @Body 전달 방식을 변경합니다.
*
* @param id 유저 고유 아이디
* @param name 유저 이름
*/
@Post('/create_user')
@UsePipes(ValidationPipe)
onCreateUser(@Body() createUserDto: CreateUserDto): User[] {
return this.userService.onCreateUser(createUserDto);
}
우리가 생성한 CreateUserDto를 @Body 데코레이터와 연결 후 @UsePipes를 사용해서 등록합니다.
name 속성에 빈 값을 전송한다면 위와 같이 "name should not be empty" 빈 값을 전달받아 에러가 발생한 걸 알 수 있습니다.
2. Parameter-level Pipes
전달받은 모든 파라미터를 검사하지 않고 특정 파라미터만 검사하는 방식입니다.
/**
* @author Ryan
* @description @Param & @Body 혼합 방식 - 단일 유저 수정
*
* @param id 유저 고유 아이디
* @param name 유저 이름
*/
@Patch('/user/:id')
@UsePipes(ValidationPipe)
setUser(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
): User {
return this.userService.setUser(id, updateUserDto);
}
@Query 데코레이터를 보면 두 번째 인자 값은 기본 값을 설정하는 부분입니다.
세 번째 인자 값은 자료형 타입을 정수형 타입이 아니면 에러를 발생합니다.
정수형 데이터 타입이 아닌 문자열 타입으로 데이터를 전송했습니다. 자료형 타입을 잘못 지정해 Error가 발생한 걸 확인할 수 있습니다.
이번 시간에는 정말 많은걸 작성했는데요... 이상하거나 또는 틀린 부분이 있어 헷갈리시는 부분들은 댓글 남겨주시면 감사하겠습니다.
소스 저장소
github: https://github.com/Ryan-Sin/Node_Nest/tree/v3
'백앤드(Back-End) > Nest.js' 카테고리의 다른 글
[Nest.js] Nest.js API 만들기 (5) - TypeORM 개념 및 설치 (0) | 2021.11.13 |
---|---|
[Nest.js] Nest.js API 만들기 (4) - 미들웨어(Middleware) (0) | 2021.11.11 |
[Nest.js] Nest.js API 만들기 (2) - Handler(@Get, @Post ...) (0) | 2021.11.09 |
[Nest.js] Nest.js API 만들기 (1) - Controller, Service, Provider (0) | 2021.11.07 |
[Nest.js] Nest.js 기본 구조 분석하기 (0) | 2021.11.06 |