Nestjs

2022년 12월 19일, 04:00

소개

Node.js server-side application 프레임워크이다.

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications.

TypeScript를 지원하고, OOP(Object Oriented Programming), FP(Functional Programming), FRP(Functional Reactive Programming)의 요소를 결합해둔 프레임워크라고한다.

NestJS는 out-of-the-box 애플리케이션 아키텍쳐를 제공하고 있다. (out-of-the-box: 기본적으로 모든 환경을 제공해줘서 사용자가 따로 커스텀할 필요 없이 사용 가능) 그래서 개발자나 팀이 사용하는 경우에 아래와 같은 이점이 있다고 한다.

  • 테스트가 쉬움
  • 확장가능함
  • 느슨한 관계
  • 쉬운 유지보수

아키텍쳐는 Angular에 영감을 많이 받았다고 한다.

구조

Controllers

Controller는 요청과 응답을 다룬다. 라우팅 알고리즘을 통해 어떤 Controller가 응답을 받을지 결정한다. Controller는 하나 이상의 route를 가지고 있으며 route들은 각각 다른 행동을 한다.

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

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

기본 컨트롤러를 정의하기 위해서는 @Controller 데코레이터를 사용해야한다. @Controller는 route할 path의 prefix나 sub-domain같은 값들을 받을 수 있다.

Controller의 각 메소드는 HTTP 요청을 처리하고 응답을 리턴한다. Nest에서 응답을 처리하는 방법은 객체, 배열인 경우에는 JSON으로 자동으로 처리하고, 원시값인 경우에는 JSON으로 처리하지 않고 반환한다. 값을 반환하기만 하면 Nest가 알아서 처리해준다.

요청을 받는 메소드에 데코레이터를 통해 여러가지 정보들을 설정해줄 수 있다.

  • HTTP Methods
    • HTTP 메소드를 지정할 수 있다.
    • ex) @Get , @Post 등등
  • Headers
    • 응답 헤더를 설정할 수 있다.
    • ex) @Header('Cache Control', 'none')
  • Status Code
    • 응답 HTTP 코드를 설정할 수 있다.
    • ex) @Redirect('https://nestjs.com', 301)
  • Redirection
    • 리다이렉트 url을 설정할 수 있다.
    • ex) @Redirect('https://docs.nestjs.com', 302)

Request Object Request 객체는 HTTP 요청에 관한 속성들을 가지고 있는데, 데코레이터를 사용하면 속성을 가져올 수 있다.

decorator plain platform-specific objects
@Request(), @Req() req
@Response(), @Res() * res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
@Ip() req.ip

Route parameters

경로에 parameter를 넣어 동적으로 값을 가져오는 경우에 아래와 같이 할 수 있다.

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

@Controller('cats')
export class CatsController {
	@Get(':id')
	findOne(@Param() params): string {
		console.log(params.id);
		return `This action returns a #${params.id} cat`;
	}
}

또는

@Get(':id')
findOne(@Param('id') id: string): string {
  return `This action returns a #${id} cat`;
}

Request payloads POST 메소드에서 클라이언트가 보낸 payload를 @Body decorator를 이용해서 처리할 수 있다.

요청을 처리하기 위해서는 DTO를 만들어야하는데 Nest는 TypeScript의 interface나 간단한 class를 사용한다.

Data Transfer Object(DTO) 계층 간 데이터 교환을 하기 위한 객체로 로직을 가지지 않고 Getter, Setter만 존재하는 순수한 데이터 객체

2가지 방법이 있지만, Class를 사용하는 것을 더 추천하고 있는데 Class는 ES6 표준이고 TypeScript가 컴파일 될 때 보존되기 때문이다. Nest에는 Pipes 같은 기능이 런타임 때 변수의 메타타입에 접근할 때 가능하게 한다고 한다.

Pipes PipeTransform을 구현하는 @Injectable() 데코레이터이다. 입력한 데이터를 원하는 타입으로 변경하는 경우나 데이터에 대한 검증을 하는 경우에 사용한다.

create-cat.dto.ts

export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

cats.controller.ts

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

Module에 등록

Controller는 항상 Module에 속해야하며 @Module 데코레이터 안에 배열에 포함되어야한다.

app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule {}

Providers

Provider는 NestJS의 기본 개념이다. Nest에서는 많은 클래스들이 Provider로 다루어지는데, 여기에는

  • Services
  • Repositories
  • Factories
  • Helpers

같은 클래스들이 있다.

Provider의 기본 아이디어는 Provider가 의존성 주입이 되는 것이다. 이때, 의존성은 Nest runtime에서 주입해준다.

Controller는 HTTP request를 다루고, 복잡한 일을 Provider에 위임한다. Provider는 class로 작성되고 Module안에서 providers에 넣어줘야한다.

Provider는 @Injectable 데코레이터를 사용한다. 이 데코레이터는 Provider가 Nest IoC 컨테이너에서 관리할 수 있는 클래스인 것을 나타내는 메타데이터를 지정해준다.

ex) cats.service.ts

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

interfaces/cat.interface.ts

export interface Cat {
  name: string;
  age: number;
  breed: string;
}

cats.controller.ts

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

Class의 생성자에서 CatsService가 주입됨.

Module에 등록

Controller와 마찬가지로 Provider도 Module에 등록해줘야한다.

app.module.ts


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

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

Modules

Module은 @Module 데코레이터를 사용한 클래스이다. @Module 데코레이터는 Nest가 Module을 애플리케이션 구조를 구성하는데 사용하는 메타데이터를 준다.

각 어플리케이션은 적어도 1개의 Module(Root Module)을 가진다. Root Module은 Nest가 애플리케이션 그래프를 만드는 시작점이다. 앱의 크기가 작으면 Root Module만 존재할 수 있지만, 여러 개를 사용하여 밀접하게 관련된 기능들을 캡슐화하여 사용하는 것을 Nest에서는 추천한다.

@Module 데코레이터는 객체를 받는데, 객체의 속성은 아래와 같다.

properties description
providers Module에서 공유될 수 있는 providers
controllers 인스턴스화 되어야하는 모듈에 정의된 Controllers
imports 해당 모듈에서 필요한 providers를 exports하는 Modules
exports providers의 subset으로 다른 모듈에서 사용할 수 있는 providers

Module은 기본적으로 providers를 캡슐화하기 때문에 exports를 하는 것을 Module의 public interface 혹은 API라고 생각해도 된다.