NestJS / / 2024. 11. 18. 03:09

NestJS DTO,Validation

1. 초기 방식 vs. DTO 사용 방식

이전의 형태: @Body()로 각각의 필드를 개별적으로 가져오는 방식

@Post('register/email')
postRegisterEmail(
  @Body('email') email: string,
  @Body('password', new MaxLengthPipe(8), new MinLengthPipe(3)) password: string,
  @Body('nickname') nickname: string,
) {
  return this.authService.registerWithEmail({
    nickname,
    email,
    password,
  });
}
  • 특징:
    • 모든 필드를 개별적으로 @Body()로 가져와야 함.
    • 각 필드마다 필요한 파이프(MaxLengthPipe, MinLengthPipe)를 설정해야 함.
    • 코드가 중복되고 새로운 필드 추가 시 수정할 부분이 많아 유지보수가 어려움.

변경된 형태: DTO 사용하여 요청 데이터를 한 번에 처리

@Post('register/email')
postRegisterEmail(
  @Body() body: RegisterUserDto
) {
  return this.authService.registerWithEmail(body);
}
  • 장점:
    • 요청 데이터를 DTO로 한 번에 받아서 처리하므로 코드가 간결해짐.
    • 새로운 필드를 추가할 경우 DTO 클래스만 수정하면 됨.
    • 유지보수가 용이해지고 가독성이 높아짐.

2. Validator의 사용 - 하드코딩 vs. Validator 통합

이전의 형태: 유효성 검사 메시지를 하드코딩

@Column()
@IsString({
    message: 'nickname은 문자열이어야 합니다.',
})
@Length(1, 20, {
    message: '닉네임은 1~20자 사이여야 합니다.',
})
nickname: string;
  • 특징:
    • 유효성 검사 메시지가 하드코딩되어 있음.
    • 여러 곳에서 동일한 메시지가 필요할 경우 중복이 발생할 가능성이 큼.

변경된 형태: Validator 통합으로 유효성 검사를 공통 함수로 관리

import { stringValidationMessage, lengthValidationMessage } from 'common/validation-messages';

@Column()
@IsString({
    message: stringValidationMessage,
})
@Length(1, 20, {
    message: lengthValidationMessage,
})
nickname: string;
  • 장점:
    • 메시지를 함수로 분리하여 여러 곳에서 재사용할 수 있음.
    • 유지보수가 용이해짐(메시지 수정 시 한 곳만 수정하면 됨).
    • 코드의 가독성과 일관성이 높아짐.

3. 기존 모델을 재사용 - 직접 정의 vs. PickType 사용

이전의 형태: DTO 직접 정의

export class RegisterUserDto {
  nickname: string;
  email: string;
  password: string;
}
  • 특징:
    • 엔티티에 이미 정의된 필드와 중복이 발생할 수 있음.
    • 코드의 중복으로 인해 변경 사항 발생 시 여러 군데를 수정해야 할 가능성이 높음.

변경된 형태: PickType을 사용하여 기존 모델 재사용

import { PickType } from "@nestjs/mapped-types";
import { UsersModel } from "src/users/entities/users.entity";

export class RegisterUserDto extends PickType(UsersModel, ['nickname', 'email', 'password']) {}
  • 장점:
    • 기존 엔티티(UsersModel)에서 필요한 필드만 선택하여 재사용함으로써 중복을 줄임.
    • 유지보수가 쉬워지고 변경 사항을 DTO와 엔티티에 동시에 반영할 수 있음.

4. Update 요청 처리 - 개별 필드로 관리 vs. DTO와 PartialType 사용

이전의 형태: Update 요청을 개별 필드로 처리

@Put(':id')
putPost(
  @Param('id', ParseIntPipe) id: number,
  @Body('title') title?: string,
  @Body('content') content?: string,
) {
  return this.postsService.updatePost(id, { title, content });
}
  • 특징:
    • 필드가 많아질수록 코드가 복잡해짐.
    • 모든 필드를 선택적으로 업데이트하기 어렵고 코드 중복이 발생할 가능성이 있음.

변경된 형태: DTO와 PartialType 사용하여 관리

import { PartialType } from "@nestjs/mapped-types";
import { CreatePostDto } from "./create-post.dto";

export class UpdatePostDto extends PartialType(CreatePostDto) {}

@Put(':id')
putPost(
  @Param('id', ParseIntPipe) id: number,
  @Body() body: UpdatePostDto,
) {
  return this.postsService.updatePost(id, body);
}
  • 장점:
    • 모든 필드를 선택적(optional)으로 처리하여 일부만 업데이트 가능.
    • 기존 DTO(CreatePostDto)를 확장해 재사용하므로 코드의 중복을 줄임.
    • 코드가 간결해지고 유지보수가 쉬워짐.

5. Validation Message 관리 - 하드코딩 vs. 함수형 메시지 분리

이전의 형태: 하드코딩된 Validation Message

@Column()
@IsString({
    message: 'title은 문자열이어야 합니다.',
})
@Length(1, 20, {
    message: 'title은 1~20자 사이여야 합니다.',
})
title: string;
  • 특징:
    • 동일한 유효성 검사 메시지가 여러 곳에 중복되어 나타날 가능성이 있음.
    • 메시지 수정 시 모든 곳을 수정해야 함.

변경된 형태: 공통 Validation Message 함수 사용

import { stringValidationMessage, lengthValidationMessage } from 'common/validation-messages';

@Column()
@IsString({
    message: stringValidationMessage,
})
@Length(1, 20, {
    message: lengthValidationMessage,
})
title: string;

Validation Message 함수 예시

import { ValidationArguments } from "class-validator";

export const stringValidationMessage = (args: ValidationArguments) => {
    return `${args.property}에는 문자열만 입력할 수 있습니다.`;
};

export const lengthValidationMessage = (args: ValidationArguments) => {
    return `${args.property}은 ${args.constraints[0]}~${args.constraints[1]} 글자 사이여야 합니다.`;
};
  • 장점:
    • 메시지를 공통 함수로 분리하여 코드 중복을 줄임.
    • 메시지 수정 시 한 곳만 수정하면 되어 유지보수성이 크게 향상됨.
    • 코드가 명확해지고 가독성이 높아짐.

6. 서비스 계층에서 DTO를 활용하여 데이터 처리

이전의 형태: 개별 데이터 직접 전달

async registerWithEmail(nickname: string, email: string, password: string) {
  const hash = await bcrypt.hash(password, HASH_ROUNDS);
  const newUser = await this.usersService.createUser({ nickname, email, password: hash });
  return this.loginUser(newUser);
}
  • 특징:
    • 서비스로 전달되는 데이터가 개별적으로 관리되며, 중복된 코드가 발생할 가능성이 높음.
    • 데이터의 구조적 명확성이 부족하여 유지보수가 어려울 수 있음.

변경된 형태: DTO를 활용하여 데이터 구조적 처리

async registerWithEmail(user: RegisterUserDto) {
  const hash = await bcrypt.hash(user.password, HASH_ROUNDS);
  const newUser = await this.usersService.createUser({ ...user, password: hash });
  return this.loginUser(newUser);
}
  • 장점:
    • DTO를 사용하여 데이터 구조가 명확해짐.
    • 서비스 코드가 간결해지고, 중복된 코드가 줄어듦.
    • 유효성 검사를 컨트롤러에서 통과한 데이터만 전달받아 데이터 무결성을 보장함.

결론: DTO와 Validator 활용의 효과적인 변화

이전의 방식에서는 필드별로 직접 데이터를 다루고 유효성 검사 메시지를 하드코딩하는 등 유지보수가 어렵고 확장성이 부족한 문제점이 있었습니다. 이를 개선하기 위해 DTO와 class-validator를 활용하여 데이터를 구조적으로 처리하고, Validator 메시지를 함수로 분리하여 재사용성을 높였습니다.

이러한 변화의 주요 장점은 다음과 같습니다:

  1. 코드 중복 제거: DTO와 Validator 통합, Validation Message 함수 사용으로 중복된 코드 제거.
  2. 유지보수 용이성: 필드 추가/수정 시 DTO나 Validator 메시지를 수정하기만 하면 되어 유지보수가 쉬워짐.
  3. 가독성 및 확장성 향상: 코드가 더 구조적이고 명확해져서, 프로젝트가 커질수록 유지보수에 유리해짐.
  4. 데이터 무결성 보장: class-validator를 사용하여 입력 데이터의 유효성을 보장.

이렇게 개선된 방식은 프로젝트가 커지거나 복잡해질수록 더 큰 장점을 발휘하며, 개발자들이 일관성 있는 코드 스타일을 유지하면서 효율적으로 작업할 수 있게 도와줍니다.

'NestJS' 카테고리의 다른 글

NestJS 페이징처리  (0) 2024.11.19
NestJS Class-transfomer  (0) 2024.11.18
NestJS 데코레이터  (0) 2024.11.16
NestJS Guard  (0) 2024.11.16
NestJS 핫리로드 트러블슈팅  (1) 2024.11.16
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유