Node.js/NestJS

[NestJS] 5. 미들웨어

턴태 2022. 9. 28. 15:52

미들웨어

미들웨어는 라우트 핸들러 이전에 사용되는 기능입니다. 미들웨어는 어플리케이션의 요청 응답 사이클에서 요청·응답 객체에 접근할 수 있으며, next() 미들웨어 기능을 사용할 수 있습니다. next 미들웨어 기능은 흔히 next라는 이름의 변수로 사용됩니다.

Nest의 미들웨어는 기본적으로 익스프레스 미들웨어와 동일합니다. 아래의 설명은 익스프레스 공식문서가 설명하는 미들웨어의 기능입니다.

더보기

미들웨어 기능은 다음과 같은 일을 할 수 있습니다.

  • 어떠한 코드든 실행합니다.
  • 요청 및 응답 객체를 변경합니다.
  • 요청 응답 사이클을 종료합니다.
  • 스택에 쌓여있는 다음 미들웨어를 불러옵니다.
  • 현재의 미들웨어가 요청-응답 사이클을 종료하지 않는다면, 반드시 next()를 사용하여 다음 미들웨어로 넘어갈 수 있도록 해야 합니다. 그렇지 않으면 요청은 전달되지 않고 중간에 머무르게 될 것입니다.

사용자가 생성하는 Nest 미들웨어는 함수나 클래스에서 @Injectable() 데코레이터를 사용하여 구현할 수 있습니다. 클래스는 NestMiddleWare 인터페이스로부터 구현해야 하며, 이외에는 다른 특별한 요구사항을 갖고 있지 않습니다. 아래의 예처럼, 클래스 메서드를 사용하여 간단한 미들웨어를 구현할 수 있습니다.

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

의존성 주입

Nest 미들웨어는 의존성 주입을 완전히 지원합니다. 프로바이더와 컨트롤러 때와 마찬가지로 동일 모듈 안에서 사용가능한 의존성을 주입할 수 있습니다.늘 그랬듯이, constructor를 통해 가능합니다.

 

미들웨어 적용

@Module 데코레이터에는 미들웨어를 위한 공간이 없습니다. 대신 configure() 메서드를 모듈 클래스에 사용하여 세팅할 수 있습니다. 미들웨어를 포함하는 모듈은 NestModule 인터페이스를 구현해야 합니다. LoggerMiddlewareAppModule에서 세팅해보겠습니다.

 

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

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('cats');
  }
}

위의 예에서는 이전에 CatsController 안에서 정의된 /cats 라우트 핸들러에 LoggerMiddleware를 세팅했습니다. 여기서 더 엄격하게 특정 요청 메서드에 대해서 미들웨어를 설정할 수 있습니다. 라우트 경로와 요청 메서드를 객체형태로 forRoutes() 메서드에 전달하여 사용하면 됩니다. 아래의 예에서 RequestMethod를 원하는 요청 메서드 타입으로 불러와서 임포트하였습니다.

import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { LoggerMiddleware } from './middlewares/logger.middleware';

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}
더보기

configure() 메서드는 async/await를 통해서 비동기로 만들 수도 있습니다.

 

라우트 와일드카드

앞서 컨트롤러에서 와일드카드를 사용해 경로를 지정한 것처럼 미들웨어의 경로에도 *(asterisk)를 사용해서 원하는 문자열의 조합으로 경로를 받을 수 있습니다.

 

미들웨어 소비자

MiddlewareComsumer는 헬퍼 클래스입니다. 헬퍼 클래스는 미들웨어를 관리하기 위해 미리 만들어 둔 수많은 메서드를 제공합니다. 이 모든 것들은 fluent 스타일로 연결될 수 있습니다. forRoutes() 메서드는 단일 문자열, 다중 문자열, RouteInfo 객체, 컨트롤러 클래스, 심지어는 여러 개의 컨트롤러 클래스를 사용할 수 있습니다. 대부분의 경우 콤마를 사용하여 구분되는 컨트롤러 리스트를 보낼 것입니다. 아래의 예는 단일 컨트롤러를 사용한 예입니다.

import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';
import { LoggerMiddleware } from './middlewares/logger.middleware';

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(CatsController);
  }
}

컨트롤러 클래스 하나를 사용해서 라우트에 미들웨어를 넣었습니다~! 아주 간편하네요 ㅎ.ㅎ

 

더보기

apply() 메서드는 한 개의 미들웨어, 혹은 여러 개의 미들웨어를 매개변수로 사용합니다.

라우트 제외

종종 특정 라우트에서 미들웨어를 제외하고 싶을 때가 있습니다. 이럴 때, exclude() 메서드로 특정 라우트를 제외시킬 수 있습니다. 이 메서드는 단일 문자열, 다중 문자열, 혹은 RouteInfo 객체를 인자로 받습니다.

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET }.
    { path: 'cats', method: RequestMethod.POST },
    'cats/*',
  )
  .forRoutes(CatsController);

위의 예와 같이, LoggerMiddleware는 CatsController에서 정의된 모든 라우트에서 작동하며, exclude() 메서드에서 제외한 라우트는 작동하지 않습니다.

 

함수형 미들웨어

LoggerMiddleware 클래스는 꽤 단순합니다. 여기에는 member도 없고, 추가적인 메서드도 없고, 의존성도 없습니다. 그러면 클래스 대신에 간단한 함수로 정의할 수는 없을까요? 사실 할 수 있습니다. 이 미들웨어 타입은 함수형 미들웨어로 불립니다. 클래스 기반의 logger 미들웨어를 함수형 미들웨어로 바꿔봅시다.

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log('Request...');
  next();
}
consumer
  .apply(logger)
  .forRoutes(CatsController);
더보기

의존성을 필요로 하지 않는 미들웨어의 대신 간단한 함수형 미들웨어를 사용해보는 것을 고려해보세요!

다중 미들웨어

위에서 언급했듯이, 순차적으로 적용되는 다중 미들웨어를 넣기 위해서, apply() 메서드 안에 콤마를 사용해서 제공하기만 하면 됩니다.

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

전역 미들웨어

미들웨어를 등록된 모든 라우트에 한번에 적용하고 싶다면, INestApplication 인스턴스로부터 use() 메서드를 사용할 수 있습니다.

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);