본문 바로가기
TIL

TIL 240625 - Nest.js란? 간단한 코드 해부하기

by lemonpie611 2024. 6. 25.

1. Nest.js를 사용하는 이유

1) Express.js의 한계

  • 바디파서, 쿠키파서 등 사용할 미들웨어들에 대해 알고있어야 코딩이 가능함.. > 복잡한 웹서버 개발 시 비효율적
  • 레이어드 아키텍처 패턴 구성 시 디렉토리 구조를 명학히 설계해야 하며, 서비스 요구사항 변경 및 기획 추가 시 필요한 개념들을 새롭게 추가해야 함

2) Nest.js의 장점

  • 타입스크립트 기반 > 자바스크립트에 비해 엄격한 타입 체크를 하여 여러 예외 상황을 사전에 방지
  • 레이어드 아키텍처 패턴 구성 시 커맨드 하나로 대부분의 구성요소 구현 가능 (ex. Posts 컨트롤러 생성 시 nest g co posts 커맨드 사용)
  • 따라서, 개발자는 웹서버의 핵심 로직 구현에만 신경쓰고 그 이외의 부분들은 Nest.js에 일임 가능

 

2. 간단한 코드 해부

1) 셋팅

  • 터미널창에 nest 입력 > nest 커맨드에 대한 매뉴얼 제공
  • nest.js로 새로운 프로잭트 생성 > npm 선택
nest new sparta-nest

 

2) 진입점 파일

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
  • ./src/main.js > 임의로 파일 이름 변경 금지!!
  • Nest.js에서 진입정으로 사용하기로 약속된 파일
  • NestFactory 클래스의 create 정적함수를 통해, AppModule이라는 모듈을 루트 모듈로 사용하는 Nest.js 어플리케이션 인스턴스 새롭게 생성

3) 모듈

// app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
  • AppModule이 정의된 app.module.ts 파일
  • Nest.js는 이런 모듈 기반으로 구성되어 있음. 모듈을 새로 만들면 ./src/(모듈이름)/(모듈이름).ts 파일이 자동으로 생성됨
  • 데코레이터
    • @가 붙는 키워드 (ex. @Module) 
    • 해당 클래스나 함수가 어떤 역할을 수행하는지 Nest.js에 알려줌
    • 미리 정의된 데코레이터는 각각 필요한 속성이 다름. @Module의 경우 imports, controllers, providers, exports 속성을 가질 수 있음
    • 직접 데코레이터를 만드는것도 가능

4) 모듈 속성

  • imports : 현재 모듈에서 사용하려는 다른 모듈들의 목록을 정의, 여기 명시된 모듈들은 주로 필요한 프로바이더(서비스)를 제공 (ex. HttpModule, TypeOrmModule 등)
  • controllers : 현재 모듈과 관련된 컨트롤러의 목록 정의, 이 컨트롤러들은 해당 모듈의 요청 처리 로직을 담당
  • providers : 현재 모듈에서 사용하거나 제공하는 서비스, 리포지토리, 팩토리 등의 목록을 정의, 여기 포함되는 것들은 주로 비즈니스 로직 처리나 데이터 액세스와 같은 작업을 수행
  • exports : 현재 모듈에서 다른 모듈로 제공하려는 서비스의 목록을 정의, 현재 모듈 외부에서도 사용할 수 있도록 함

5) imports / exports와 service(only) / providers 의 차이

  • service(only) + providers 는 서비스가 특정 모듈 내에서만 사용되고 다른 모듈에서는 사용되지 않을 때 주로 사용
  • module exports + imports : 여러 모듈에서 공통적으로 사용할 때 주로 사용

6) 컨트롤러

// app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

 

  • @Controller 데코레이터를 통해 AppController 클래스가 컨트롤러 역할을 하는 것을 Nest.js에 알려줌
  • 의존성 주입(constructor 부분) : 인자로 AppService 객체를 넘기면 this.appService라는 멤버 변수에 AppService 객체가 주입됨
  • 컨트롤러는 반드시 서비스를 의존해야 함. 또한, 이는 생성자를 통한 의존성 주입(DI)로 해결해야 함

7) 컨트롤러의 데코레이터

  • @Get : HTTP GET 으로 요청이 들어올 시 이 아래 함수로 실행함
  • @Post, @Get, @Put, @Patch, @Delete 등.. 사용 가능

8) 서비스

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

@Injectable() // <- 난 Inject(주입)될 수 있어! 라고 선언하는 것이에요.
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
  • @Injectable 데코레이터가 있어야 컨트롤러에서 AppController의 생성자로 AppService를 주입할 수 있음
  • 서비스 객체는 실제로 리포지토리를 의존하며 비즈니스 로직을 담당 > 서비스는 리포지토리를 반드시 의존해야 하며, 이는 생성자를 통한 DI로 해결해야 함

 

3. IoC와 DI

1) 기존에 사용한 방법

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  // 1. 사용하고 싶은 서비스 타입 객체를 미리 선언합니다.
  private appService: AppService
  
  constructor() {
    // 2. 생성자에서 실제로 사용할 서비스 객체를 직접 생성합니다.
    this.appService = new AppService();
  }
  ...
}
  • 개발자가 사용할 객체를 생성부터 소멸까지 직접 관리
  • AppController는 AppService의 구체적인 구현에 강하게 결합됨 > 의존하는 서비스가 변경될 경우 그에 맞춰서 코드도 수정해야 함 > 번거로움과 부작용

2) IoC (Inversion of Control, 제어 역전)

  • 개발자가 사용하고 싶은 객체를 직접 생성하지 않고, 객체의 생명주기 관리 자체를 외부(위 예시의 경우 Nest.js IoC 컨테이너)에 위임
  • 객체의 관리를 컨테이너에 맡겨서 제어권이 넘어감
  • AppController는 AppService의 구체적인 표현보다는 인터페이스나 추상 클래스에 의존 > 서비스 자체가 변경되어도 관계없이 사용 가능 > 코드 결합도 감소, 다른 구현체로 쉽게 교체 가능

3) DI (의존성 주입)

  constructor(private readonly appService: AppService) {} // 살포시 연착륙
  • 의존성 주입 메커니즘으로 AppService를 AppController에 주입하여 IoC 원칙 적용
  • constructor에서 AppService의 인스턴스는 Nest.js의 DI 컨테이너에 의해 생성되고 관리됨
  • 따라서 개발자가 직접 new AppService와 같이 객체를 생성하거나 관리하지 않아도 됨