NestJS Logo

원문을 읽으며 정리합니다.

Table of Contents

  1. Providers

Providers

Provier는 NestJS에서 핵심적인것 중 하나이다. 많은 기본적인 NestJS 클래스들은 Provider로써 다뤄진다.(service, repository, factory, helper, 등). Provider의 메인 아이디어는 의존성 주입을 할 수 있다는 것이다. 따라서 객체들은 다른 객체들과 다양한 관계를 맺을 수 있다. Provider로써 Dependency를 주입 할수 있게 간단하게 @Injectable() 데코레이터를 붙여주면 된다.

Services

CatsService를 만들어 보자. 이 서비스는 데이터 저장소와 회수를 책임진다. CatsController에서 사용되는데 Provider로써 좋은 후보다. 그러므로 @Injectable() 데코레이터를 붙여주자.

$ nest g service cats
// src/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;
  }
}

@Injectable()를 붙여줌으로써 NestJS는 이 클래스가 provider임을 안다.

추가로 아래 인터페이스를 추가해주자.

// src/cats/interfaces/cat.interface.ts
export interface Cat {
  name: string;
  age: number;
  breed: string;
}

이제 컨트롤러에서 서비스를 사용해보자.

// src/cats/cat.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();
  }
}

CatsService가 class 생성자를 통해 inject 되었다. 그리고 생성자에서 사용한 문법은 여길 참고하자.

우연치 않게 typedi라는 재밌는 녀석도 알게 되었다.

Scopes

Provider는 보통 애플리케이션 생명주기와 동기화된 "scope" 생명주기를 가진다. 애플리케이션이 bootstrap 되면 의존성은 평가되고 인스턴스화 된다. 그리고 종료할때 같이 파괴된다. 그러나 request-scoped 생명주기(요청이 들어올때마다 새로운 객체 생성) 또한 만들 수 있다. 이 기술은 여길 참고하자.

Custom providers

NestJS는 IoC(Inversion of Control, 제어 역전) 컨테이너가 존재한다(Spring Boot가 생각났다.). @Injectabl() 데코레이터만 Provider를 정의하는 방법은 아니고 일반적인 값, 클래스 그리고 동기 비동기 팩토리를 사용할 수도 있다. 자세한 내용은 여기서 확인하자.

Optional providers

선택적으로 사용되는 의존성이 있을 수 있다. 그럴땐 @Optionall() 데코레이터를 사용하자.

import { Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

위 예시는 커스텀 프로바이더를 사용했다. @Inject()랑 처음보는게 나오는데 문서에 있던거라 잘 모르겠다.

Property-based injection

위에서 사용했던 DI 기술들은 생성자 기반이였다. 어떤 경우엔 Property-based injection이 매우 유용할 지도 모른다. 예를 들어, 최상위 클래스가 하나 또는 여러개의 provider에게 의존한다면 우리는 super()를 서브 클래스마다 계속 호출해야 한다. 이를 해결하기 위해 @Inject() 데코레이터를 프로퍼티에 바로 사용할 수도 있다.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

공식 문서에서는 클래스가 다른 provider에 확장하지 않는다면 항상 생성자 기반 injection을 사용할 것을 추천한다고 한다.

Provider registration

위 과정을 통해 CatsService Provider를 생성했고 이를 CatsController에서 사용할 것이다. 이제 이 Provider를 NestJS에서 주입할 수 있도록 등록시켜 줘야 한다. 이는 아래와 같이 app.module.ts에서 추가하면 된다.

// 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 {}

이제 NestJS는 CatsController에 DI 할 수 있다.

지금까지 작성한 프로젝트 구조는 아래와 같다.

src
├─ cats
│  ├─ dto
│  │  └─ create-cat.dto.ts
│  ├─ interfaces
│  │  └─ cat.interface.ts
│  ├─ cats.service.ts
│  └─ cats.controller.ts
├─ app.module.ts
└─ main.ts
Next NestJS OVERVIEW(4) - Module
Intro NestJS OVERVIEW(0) - Intro