기억의 실마리
2023. 1. 24. 15:14

Nest.js

Nest.js는 Node.js기반의 웹  API 프레임워크로서 Express, Fastify 프레임워크를

래핑하여 동작한다. 기본적으로는 Exress를 제공한다.

 

Nest.js의 장점

Node.js는 사용하기에 있어 편리하고 뛰어난 확장성을 가지고 있다.
이러한 특성으로 SW의 품질이 일정하지않고 라이브러리를 찾기위해
사용자가 많은 시간을 할애해야 한다. 반면 Nest.js는 데이터베이스,
ORM, Configuration, 유효성 검사 등 수많은 기능을 기본으로 제공하고 있다.
그러면서도 필요한 라이브러리를 쉽게 설치가능하기 떄문에

Node.js의 장점도그대로 가지고 있다.

 

 

Nest.js의 특징

Angular로부터 영향을 받아 모듈,컴포넌트 기반으로 프로그램을 작성함으로서

재사용성을 높인다. IoC(Inversion of Control), DI(Dependency Injection),

AOP(Aspect Oriented Programming)와 같은 객체지향 개념을 도입했다.

언어는 타입스크립트를 기본으로 채택하고 있다.

 

사용예시

Nest.js Install

npm i -g @nestjs/cli
//인스톨이 잘 되었는지 터미널에 nest를 입력해서 확인해본다.

인스톨이 잘 된 경우 터미널에 nest를 입력시 nest의 리스트가 나오게 된다.

 

nest new Project
//Project이름을 가진 Default create-nest가 생성된다.

프로젝트를 새로 만들어야 할 경우 Nest.js의 createt기능을 사용하여

Default프로젝트를 만들 수 있다.

 

 

src 폴더 구조

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: true,
      forbidNonWhitelisted: true,
      transform: true,
      //transform은 요청데이터를 필요한 데이터형태로 바꿔준다. 원래는 string으로 받지만 number로 바꿔주고 있다.
    }),
  );
  await app.listen(3000); //run start시에 사용되는 서버포트이다.
}
bootstrap();

 

app.modules.ts

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

import { Controller, Get } from '@nestjs/common';

@Controller('')
export class AppController {
  @Get()
  home() {
    return 'Welcome to my Movie API';
  }
}

src폴더는 Nest.js에서 root가 되는 구조이며 service.ts파일은 현재 사용예시 구조상 필요하지않고

movies폴더 내부에서 실질적인 구현을 대신하고 있으므로  movies폴더 내부에 service.ts파일이 있다.

 

 

movies 폴더구조

dto

dto는 코드를 간결하게 만들어 줄 수 있고 클라이언트로 부터 request를 받을때

유효성 검사를 해주기 위함이다.

 

create-movie.dto.ts

import { IsNumber, IsOptional, IsString } from 'class-validator';

export class CreateMovieDTO {
  @IsString()
  readonly title: string;

  @IsNumber()
  readonly year: number;

  @IsOptional()
  @IsString({ each: true })
  readonly genres: string[];
}
//DTO를 만드는이유는 코드를 간결하게 해주고 NestJS가 들어오는 쿼리에 대해 유효성을 검사할 수 있게 도와준다.

 

update-movie.dto.ts

import { PartialType } from '@nestjs/mapped-types';
import { CreateMovieDTO } from './create-movie.dto';

export class UpdateMovieDTO extends PartialType(CreateMovieDTO) {}

 

entities

entities는 DataBase에 넣어 줄 스키마를 미리 지정해주는 역할을 한다.

현재 예시는 간단하게 클래스를 지정하여 만들어 주었지만 실제 서비스에서는

typeORM을 사용하여 직접적으로 DB에 스키마를 만들어 줄 수 있으며

이 외에도 Prisma, Sequelize ORM 등이 있다.

movie.entity.ts

export class Movie {
  id: number;
  title: string;
  year: number;
  genres: string[];
}

 

movies 내부

movies.controller.ts

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

@Controller('movies')
export class MoviesController {
  constructor(private readonly moviesService: MoviesService) {}
  /* movies.module.ts에서 @Module을 사용해서 controllers와 providers에서 import시켜주었기 때문에
  constructor(private readonly moviesService: MoviesService)와 같이 타입을 추가하는 것만으로
  같이 사용할 수 있게 되었다. 이것을 dependency injection이라고 한다.
   */

  @Get()
  getAll(): Movie[] {
    return this.moviesService.getAll();
  }

  @Get(':id')
  getOne(@Param('id') movieId: number): Movie {
    //필요한 것이 있을땐 반드시 요청을 해야한다. @Param()을 통해서 요청을 하고 props로 받는 구조다.
    console.log(typeof movieId);
    return this.moviesService.getOne(movieId);
  }

  @Post()
  create(@Body() movieData: CreateMovieDTO) {
    //@Body를 사용해서 클라이언트에서 보낸 object형식의 JSON을 추가할 수 있다.
    return this.moviesService.create(movieData);
  }

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

  @Patch(':id') //데코레이터 Put과 Patch두가지가 있는데 Put은 모든 리소스를 받아오기 때문에 적합하지 않을 수 있다.
  patch(@Param('id') movieId: number, @Body() updateData: UpdateMovieDTO) {
    return this.moviesService.update(movieId, updateData);
  }

 

movies.module.ts

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

@Module({
  controllers: [MoviesController],
  providers: [MoviesService],
})
export class MoviesModule {}

 

movies.service.ts

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

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

  getAll(): Movie[] {
    return this.movies;
  }
  //가짜 데이터베이스이며, 진짜데이터베이스에서는 Query를 가져온다.

  getOne(id: number): Movie {
    const movie = this.movies.find((movie) => movie.id === id);
    if (!movie) {
      throw new NotFoundException(`Movie with ID: ${id} not found.`);
      //HttpException에서 확장된 NestJS의 제공기능. 예외처리 기능을 한다.
    }
    return movie;
  }

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

  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 });
  }
}

service.ts에서의 역할은 주로 서버에서 필요한 function의 집합체라고 이해할 수 있다.

가장 상위에서 @Injectable 데코레이터를 통해서 module내부에 provider로서

기능들을 주입할 수 있다.

 

 

마치며...

아키텍쳐를 정해두고 블록처럼 쌓아가는 형식으로 만들 수 있는것은 협업을 했을땐 오히려 가독성이 좋고 오차를 줄일 수 있는 장점이 있을 것 같다. 또한 프로그래밍의 방식을 고민해야할 것들이 이미 가이드라인으로 잡혀있다는 것 자체도 협업을 하는 시점에서 오해의 여지가 줄어들어 작업 효율이 좋아질 것 같아 보였다.

 

https://github.com/zeriong/Nest_intro