Node

우리가 NestJS를 사용해야하는 이유

NEMNE 2022. 2. 6. 17:29

NestJS란?

최근 몇 년 동안 Node.js 덕분에 JavaScript는 BE, FE 애플리케이션 모두 웹의 공통 언어가 되었습니다. 이로 인해 Angular, React, Vue가 나오게 되었으며, 해당 프로젝트를 통해 생산성을 향상하고 빠르게 만들 수 있으며, 테스트 가능하고 확장성이 있는 프런트엔드 애플리케이션을 만들 수 있게 되었습니다. 그러나 서버 측 Node.js에서는 뛰어난 라이브러리, 툴이 존재하지만 아키텍처의 주요 문제를 효과적으로 해결하는 것은 없었습니다.

Nest는 개발자와 팀이 테스트 가능하고 확장이 가능하며, 느슨한 결합과 유지보수성이 뛰어난 애플리케이션을 만들 수 있도록 아키텍처를 제공합니다. 이 아키텍처는 Angular(느슨한 결합과 뛰어난 확장성을 가짐)에서 영감을 받았습니다.

해당 글은 NestJS 공식 홈페이지에서 발췌한 글입니다.

 

위 글의 핵심은 NestJS는 서버 측 어플리케이션 개발에 있어 아키텍처의 문제를 해결하기 위해 등장한 것입니다.

 

Express는 사용하기도 쉽고 성능도 뛰어나지만 아키텍처에 관한 정의나 기능을 제공해주고 있진 않습니다.

 

저 역시 예전에 Node.js에 Express를 사용한 프로젝트에서 서버 아키텍처에 대한 고민을 팀원들과 깊게 해 본 적이 있습니다.

그러다 보니 자연스럽게 효율적인 아키텍처에 대한 고민도 해보고 여러 자료를 살펴보기도 했습니다. 하지만 찾을 때마다 대체로 비슷하지만 조금씩 다른 느낌을 받았습니다.

 

 

실제로 위 사진은 Express를 기반으로 한 프로젝트인데요.

router -> controller -> entity, routes -> controller -> db, routes -> service -> model 등 다양한 구조로 개발한 것을 알 수 있습니다. 

 

실제로 팀 또는 사람마다 아키텍처가 다르면 이를 이해하기 위한 비용 또는 개발 전에 아키텍처를 선정하는 커뮤니케이션 비용이 증가합니다.

 

그래서 NestJS는 아키텍처에 대한 정의를 제공하기 때문에 동일한 아키텍처에서 다른 개발자가 작성한 코드를 쉽게 이해할 수 있습니다.

 

 

NestJS 구성

NestJS는 Module이 존재합니다. Module은 밀접하게 관련된 기능 집합으로 구성 요소를 구성하는 효과적인 방법입니다. Module에는 하나의 비즈니스 로직을 담고 있으며 기본적으로 싱글톤이므로 여러 Module 간에 쉽게 공급자의 동일한 인스턴스를 공유할 수 있습니다.

 

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { DogsModule } from '../dogs.module';

@Module({
  imports: [DogsModule],	// 싱글톤으로 다른 모듈을 가져올 수 있다.
  controllers: [CatsController], // 인스턴스화 해야하는 해당 모듈에 정의된 controller
  providers: [CatsService],	// Nest injector에 의해 인스턴스화 되고 해당 모듈에서 사용할 provider
  exports: [CatsService] // providers에서 제공하는 모듈이 포함되어야하며 다른 모듈에서도 사용할 수 있게 해줌
})
export class CatsModule {}

 

 

Module안에는 Controller가 존재합니다. Controller는 요청을 처리하고 응답을 반환하는 역할을 합니다. Express의 route폴더를 생성하는 것과 비슷합니다.

 

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

@Controller('cats')
export class CatsController {
  @Get()	// 요청 받는 url 및 method 지정
  findAll(): string {
    return 'This action returns all cats';
  }
}

여기서 return 값으로 문자열이 반환되지만 nest에서는 자동으로 JSON으로 직렬화하여 값을 보내줍니다.

또한 추가로 req, res 객체를 얻고 싶다면 @Req(), @Res(), @Body()등의 데코레이터를 사용하여 받아올 수 있습니다.

 

그다음으로 Provider입니다. Provider의 핵심은 종속성 주입이 가능하다는 것입니다. 종속성 주입이란 Controller에는 여러 서비스들이 필요할 수 있는데 여기서 Controller안에 서비스를 넣는 것을 의미합니다. (여기서 서비스는 @Injectable 데코레이터로 감싸 져서 모듈에 제공되며 Controller에서 데이터의 유효성을 체크하거나 데이터베이스에 아이템을 생성하는 등의 작업을 진행합니다.)

또한 Provider는 서비스 뿐만 아니라 레포지토리, 팩토리, 헬퍼 등이 Provider로 취급됩니다.

 

import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

 

NestJS  장점

 

1. 사용하기가 쉽다.

 

기존에 Express는 express, jest, supertest, eslint, prettier, @types/.. 등 프로젝트 시작 전 설치할 라이브러리가 너무 많을 뿐만 아니라 폴더를 하나씩 하나씩 생성해야 한다는 점이 비효율적입니다.

 

그러나 NestJS에서는 

 

npm i -g @nestjs/cli

nest new project-name

nest g service service-name
nest g module module-name

cli를 전역적으로 설치 후 nest라는 명령어를 통해 프로젝트를 만들거나 서비스, 모듈 파일 및 테스트 파일을 생성해줌으로써 간편하게 프로젝트를 사전에 설정할 수 있습니다.

 

2. 개발하는데에 자주 사용하는 기능들을 NestJS에서는 내장되어 있다.

 

IoC나 유효성 검사를 해주는 기능들은 기존에 Express에서는 Joi, typeDI를 통해 설치를 해야 했지만 NestJS는 이러한 기능들이 데코레이터를 통해 사용할 수 있습니다.

 

// ./boards.controller.ts
...
import {
  Body,
  Controller,
  Delete,
  Get,
  Logger,
  ParseIntPipe,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { BoardStatusValidationPipe } from './pipes/board-status-validation.pipe';

@Controller('boards')
@UseGuards(AuthGuard())
export class BoardsController {
  private logger = new Logger('BoardsController');
  constructor(private boardService: BoardsService) {}
  
  ...

  @Patch('/:id/status')
  updateBoardById(
    @Param('id', ParseIntPipe) id: number,
    @Body('status', BoardStatusValidationPipe) status: BoardStatus,
  ): Promise<Board> {
    return this.boardService.updateBoardById(id, status);
  }
}


// board-status-validation.pipe.ts


import {
  ArgumentMetadata,
  BadRequestException,
  PipeTransform,
} from '@nestjs/common';
import { BoardStatus } from '../board-status.enum';

export class BoardStatusValidationPipe implements PipeTransform {
  readonly StatusOptions = [BoardStatus.PRIVATE, BoardStatus.PUBLIC];

  transform(value: any, metadata: ArgumentMetadata) {
    value = value.toUpperCase();

    if (!this.isValidValue(value))
      throw new BadRequestException('잘못된 형식입니다.');

    return value;
  }

  private isValidValue(value: any) {
    const index = this.StatusOptions.indexOf(value);
    return index !== -1;
  }
}

 

4. 오픈소스이며 매년 사용자들이 증가하고 있다.

2022년에 들어서는 현재 120만에 가까운 다운로드 수치를 보여주고 있으며 이는 2021년 대비 3배가량 상승했습니다.

사용자가 증가할 수록 관련 커뮤니티들이 활성화되고 NestJS에 관한 자료들을 쉽게 찾을 수 있습니다.

 

3. 아키텍처가 정의되어 있다.

 

위에서 설명한대로 NestJS는 아키텍처가 정의되어 있어 여러 개발자들 간에 협업하기에도 좋으며 이는 대규모 엔터프라이즈 애플리케이션을 개발을 할 때 더 효율적으로 적용됩니다.