HooneyLog
© 2026 Seunghoon Shin. All rights reserved.
모든 게시글
nest.js
2022. 5. 22.•
10

Nest.js에서 dto를 사용하여 validation과 데이터 변형 시켜보기

Seunghoon Shin
작성자 Seunghoon Shin풀스택 개발자

이번 글에서는 타입스크립트 백엔드 프레임워크인 Nest.js에서 POST나 PUT과 데이터 요청이 들어왔을때 그 데이터가 적절한 데이터인지 확인하는 validation과

GET을 했을때 서버 데이터에서 가지고 있는 raw데이터 형식이 아니라 중간에서 Interceptor하여 다른 형태의 데이터로 변환 시켜 줄 수 있는 transform을 한번 해보려고 한다.

먼저 테스트 하기 위한 데이터를 ts파일에서 넣는다.

Test Data

export type ReportType = 'income' | 'expense'; export interface Reports { reports: { id: string; source: string; amount: number; created_at: Date; updated_at: Date; type: ReportType; }[]; } export const data: Reports = { reports: [ { id: 'uuid', source: 'Salary', amount: 7500, created_at: new Date(), updated_at: new Date(), type: 'income', }, { id: 'uuid232', source: 'Salary', amount: 7500, created_at: new Date(), updated_at: new Date(), type: 'income', }, { id: 'uuid2', source: 'Youtube', amount: 300, created_at: new Date(), updated_at: new Date(), type: 'expense', }, { id: 'uuid3', source: 'Facebook', amount: 7500, created_at: new Date(), updated_at: new Date(), type: 'expense', }, ], };

데이터 구조는 아래와 같이 이루어져있고, POST를 통해 데이터를 삽입하는 것으로 예시를 들어보겠다.

Controller

@Post() createReport(@Param('type') pType: ReportType, @Body() body: Report) { return this.appService.createReport(pType, body); }
  • pType이라는 매개변수로 클라이언트에서 보낸 type 값이 들어온다.
  • 위와 마찬가지로 body안에도 클라이언트가 보낸 body 객체가 들어온다.

Service

createReport(pType: ReportType, body: Report) { if (!isDataType(pType)) { return { message: '잘못된 타입' }; } const newReport: Reports['reports'][number] = { id: uuid(), created_at: new Date(), updated_at: new Date(), type: pType, ...body, }; data.reports.push(newReport); return data; }
  • 컨트롤러에서 들어온 데이터를 받아 비즈니스 로직을 처리하고 return하여 controller에 보내준다
  • controller는 이 데이터를 받아 클라이언트에 보여준다

문제점

위와 같은 형식으로 컨트롤러를 처리하면 아직 validation이 안되는 상태이기때문에 잘못된 데이터가 들어와도 에러가 발생하지 않고 컨트롤러 진입하게된다.

빈 source와 음수인 amount 데이터를 요청
빈 source와 음수인 amount 데이터를 요청

위 요청에 따른 응답값 (맨 아래 보면 잘못된 데이터가 들어가 리턴된 모습을 볼 수 있다 )

{ "reports": [ { "id": "uuid", "source": "Salary", "amount": 7500, "created_at": "2022-05-22T02:07:35.647Z", "updated_at": "2022-05-22T02:07:35.647Z", "type": "income" }, { "id": "uuid232", "source": "Salary", "amount": 7500, "created_at": "2022-05-22T02:07:35.647Z", "updated_at": "2022-05-22T02:07:35.647Z", "type": "income" }, { "id": "uuid2", "source": "Youtube", "amount": 300, "created_at": "2022-05-22T02:07:35.647Z", "updated_at": "2022-05-22T02:07:35.647Z", "type": "expense" }, { "id": "uuid3", "source": "Facebook", "amount": 7500, "created_at": "2022-05-22T02:07:35.647Z", "updated_at": "2022-05-22T02:07:35.647Z", "type": "expense" }, { "id": "d283f562-7598-41ab-8321-4a3ce28c0b7b", "created_at": "2022-05-22T02:07:40.157Z", "updated_at": "2022-05-22T02:07:40.157Z", "type": "income", "amount": -1, "source": "" } ] }

Dto를 사용하여 Validation 하기

만약 오로지 양수인 amount와 반드시 입력된 source가 있는 데이터를 받고 그 외 데이터가 들어오면 오류를 발생시키려면 어떻게 해야할까?

src → dto → report.dto 생성 후 아래와 같이 입력

import { IsNotEmpty, IsNumber, IsPositive, IsString, } from 'class-validator'; export class CreateReportDto { @IsNumber() @IsPositive() amount: number; @IsString() @IsNotEmpty() source: string; }
  • CreateReportDto 라는 클래스를 만든다
  • class-validator 모듈 안에 들어있는 validate를 하기위한 데코레이터들을 import 한다
  • 원하는 validation을 해준다

IsNumber → 오직 숫자만

IsPositive → 오직 양수만

IsString → 오직 문자만

IsNotEmpty → 무조건 값이 필요

해당 Dto를 컨트롤러에 적용

body타입에 해당 Dto 인스턴스 타입을 넣어준다

@Post() createReport( @Param('type') pType: ReportType, @Body() body: CreateReportDto, ) { return this.appService.createReport(pType, body); }

성공적인 validation ( 데코레이터로 지정하지 않은 데이터가 들어오면 400 에러를 뱉어준다 )
성공적인 validation ( 데코레이터로 지정하지 않은 데이터가 들어오면 400 에러를 뱉어준다 )


요청이 들어오고 응답을 Interceptor하여 변형된 응답값을 리턴해주고 싶을땐 어떻게 해야할까?

아이디 값을 하나 받아 하나의 데이터를 리턴해주는 GET

컨트롤러

@Get(':id') getReportById(@Param('type') pType: ReportType, @Param('id') pId: string) { return this.appService.getReportById(pType, pId); }

서비스

getReportById(pType: ReportType, pId: string): ReportResponseDto { const result = data.reports .filter(({ type }) => type === pType) .find(({ id }) => id === pId); return result; }

응답값

Untitled.png
Untitled.png

만약 위 처럼 서버에 저장되어있는 데이터 형태 그대로 말고 좀 다르게 변형된 형태로 return 해주고 싶으면 어떻게 해야할까?

예를들어 updated_at이라는 데이터는 응답이 안내려가고 created_at이라는 키는 camel case로 createdAt으로 내려주고 싶다면 말이다.

이것또한 Dto를 사용하여 만들어 줄 수 있다.

Dto Class 만들기

export class ReportResponseDto { id: string; source: string; amount: number; @Expose({ name: 'createdAt' }) created_at: Date; @Exclude() updated_at: Date; type: ReportType; constructor(partial: Partial<ReportResponseDto>) { Object.assign(this, partial); } }
  • Exclue 데코레이터는 해당 키값을 제외 시킨다
  • Expose는 해당 키 대신에 설정한 name 프로퍼티의 값을 리턴해준다
  • 생성자를 설정한 이유는 기존 데이터를 받아 새롭게 변형된 형태의 객체를 리턴해주기 위함이다.

서비스 로직

getReportById(pType: ReportType, pId: string): ReportResponseDto { const result = data.reports .filter(({ type }) => type === pType) .find(({ id }) => id === pId); return new ReportResponseDto(result); }
  • ReportResponseDto를 리턴 타입으로 설정
  • new ReportResponseDto(result) 처럼 새롭게 만든 dto의 인스턴스 객체를 뱉어준다 ( 그러면 dto class에 정의한 객체가 나온다 )

Trasnform과 Intercept를 위한 환경 설정

main.ts 에 옵션 추가

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({ transform: true, transformOptions: { enableImplicitConversion: true, }, }), ); await app.listen(3000); } bootstrap();
  • transform을 true로 설정하여 변형 작업을 하겠다는 설정
  • transformOptions를 설정하여 절대적 변형을 사용하겠다는 설정

app.module.ts

import { ClassSerializerInterceptor, Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [ AppService, { provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor, }, ], }) export class AppModule {}
  • providers 내 provide: APP_INTERCEPTOR 를 사용하여 서비스 단에서 interceptor 를 실행하겠다는 설정
  • useClass 옵션을 사용하여 지정한 클래스에 정의된 dto를 사용하겠다는 설정

이상 Dto의 개념과 dto를 사용하여 validation과 transform 작업을 하는 방법을 알아보았다.

← 이전 글TypeScript 객체 업데이트의 정석: Spread 연산자와 Utility Types(Pick, Partial) 활용하기
다음 글 →CSS Grid 환경에서 Text Ellipsis(white-space: nowrap)가 동작하지 않는 이유와 해결 방법