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

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)가 동작하지 않는 이유와 해결 방법