Nest.js

[Nest.js] DTO, 모듈화, ValidationPipe 실습

Hoo_Dev 2023. 1. 12. 15:35
정보 전달의 포스팅 보단 학습 메모 목적의 포스팅 입니다. 😢
부족한 설명과 내용에 대해 미리 양해 부탁드립니다.

 

DTO란?

  • 데이터 전송 객체를 의미, 계층 간 데이터 교환을 위해 사용하는 객체.
  • controller와 service의 create함수의 타입에 DTO를 지정

DTO 작성, Validation 체크 (npm i class-valitator)

// create-movie.dto.ts

import { IsNumber, IsOptional, IsString } from 'class-validator';
// DTO란?
// 데이터 전송 객체를 의미, 계층 간 데이터 교환을 위해 사용하는 객체.
// controller와 service의 create함수의 타입에 DTO를 지정
export class CreateMovieDto {
  @IsString()
  readonly title: string;
  @IsNumber()
  readonly year: number;
  // each 옵션은 모든 요소를 하나씩 검사하겠다는 뜻
  @IsOptional()
  @IsString({ each: true })
  readonly genres: string[];
}
// update-movie.dto.ts

import { IsNumber, IsString } from 'class-validator';
import { PartialType } from '@nestjs/mapped-types';
import { CreateMovieDto } from './create-movie.dto';

// npm i @nestjs/mapped-types
// create의 DTO와 update의 DTO는 required의 여부에만 차이가 있다. (create는 필수 옵션들이지만 update는 일부만 수정도 가능하기 때문.)
// PartialType의 인자 안에 CreateMovieDto를 넣어주게 된다면 옵셔널하게 수정이 가능하다.
export class UpdateMovieDto extends PartialType(CreateMovieDto) {}

Controller 코드와 Service 코드

// movies.controller.ts

import { Controller, Delete, Get, Param, Post } from '@nestjs/common';
import { Body, Patch } from '@nestjs/common/decorators';
import { CreateMovieDto } from './dto/create-movie.dto';
import { UpdateMovieDto } from './dto/update-movie.dto';
import { Movie } from './entities/movie.entity';
import { MoviesService } from './movies.service';

@Controller('movies')
export class MoviesController {
  constructor(private readonly moviesService: MoviesService) {}

  // NestJS는 Express 위에서 돌아가기 때문에 컨트롤러에서 Req, Res객체가 필요하면 사용할 수 있음
  // getAll(@Req() req, @Res() res): Movie[] { res.json() ... 와 같이 사용 가능
  // 하지만 Express 객체를 직접적으로 사용하는 건 좋은 방법이 아님 (두 개(express, fastify)의 프레임워크랑 작동하기 때문)
  @Get()
  getAll(): Movie[] {
    return this.moviesService.getAll();
  }
  // 주의 해야 할 점
  // :id 와 같은 동적 라우팅을 가진 getOne함수 아래에 같은 Get 메소드인 search함수가 있다면 search의 값이 동적 라우팅의 값이 돼버린다.
  @Get(':id')
  getOne(@Param('id') movieId: number): Movie {
    return this.moviesService.getOne(movieId);
  }

  @Post()
  cerate(@Body() movieData: CreateMovieDto) {
    return this.moviesService.create(movieData);
  }

  @Delete(':id')
  remove(@Param('id') movieId: number) {
    return this.moviesService.deleteOne(movieId);
  }

  @Patch(':id')
  patch(@Param('id') movieId: number, @Body() updateData: UpdateMovieDto) {
    return this.moviesService.update(movieId, updateData);
  }
}
// movies.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateMovieDto } from './dto/create-movie.dto';
import { UpdateMovieDto } from './dto/update-movie.dto';
import { Movie } from './entities/movie.entity';

@Injectable()
export class MoviesService {
  private movies: Movie[] = [];

  getAll(): Movie[] {
    return this.movies;
  }

  getOne(id: number): Movie {
    const movie = this.movies.find((movie) => movie.id === id);
    if (!movie) {
      throw new NotFoundException(`Movie with ID: ${id} `);
    }
    return movie;
  }

  deleteOne(id: number): boolean {
    this.getOne(id);
    this.movies = this.movies.filter((movie) => movie.id !== id);
    return true;
  }

  create(movieData: CreateMovieDto) {
    this.movies.push({
      id: this.movies.length + 1,
      ...movieData,
    });
  }

  update(id: number, updateData: UpdateMovieDto) {
    const movie = this.getOne(id);
    this.deleteOne(id);
    this.movies.push({ ...movie, ...updateData });
  }
}

파일 모듈화

// movies.module.ts

import { Module } from '@nestjs/common';
import { MoviesController } from './movies.controller';
import { MoviesService } from './movies.service';

@Module({
  // 기능 별 모듈화 시키기
  // movies의 controller와 service를 movies.module로 묶은 후 app.module에 import 항목 안에 넣어준다.
  controllers: [MoviesController],
  providers: [MoviesService],
})
export class MoviesModule {}
import { Module } from '@nestjs/common';
import { MoviesModule } from './movies/movies.module';
import { AppController } from './app.controller';

@Module({
  imports: [MoviesModule],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}

파일 구조

│  app.controller.ts
│  app.module.ts
│  main.ts
│
└─movies
    │  movies.controller.ts
    │  movies.module.ts
    │  movies.service.ts
    │
    ├─dto
    │      create-movie.dto.ts
    │      update-movie.dto.ts
    │
    └─entities
            movie.entity.ts

위와 같은 코드를 통해 DB를 연결하지 않은 서버에서만 동작하는 API를 만들어 보았다.(간단하게 테스트 하는 용도)

이번 Nest.js 를 통해 REST API를 구축하고, DB와의 연결, 유효성 체크 등 백엔드의 지식을 함양할 수 있게 되는 계기가 됐으면 좋겠다.

 

현재까지 공부의 핵심

  • Nest.js 는 Express의 단점을 보완해주는 좋은 프레임워크
  • 여러 데코레이터들을 통해 CRUD를 작성할 수 있고, ValidationPipe를 통해 보다 쉽게 유효성을 검사할 수 있다. (https://www.npmjs.com/package/class-validator class-validation공식 문서)
  • ValidationPipe의 whitelist, forbidNonWhitelisted, transform 옵션들과 DTO를 통해 클라이언트와 서버간의 데이터 전송방식 에서의 편리성을 가진다.
  • 모듈화를 통해 보다 좋은 파일구조를 가질 수 있다.