lottie
Seungjun's blog
blog
nestjs docs 6편

Middleware

미들웨어는 라우트 핸들러 이전에 호출되는 함수입니다. 미들웨어 함수는 요청(request)과 응답(response) 객체에 접근할 수 있으며, 애플리케이션의 요청-응답 주기에서 다음(next) 미들웨어 함수도 사용할 수 있습니다. 다음 미들웨어 함수는 일반적으로 next라는 변수로 표시됩니다.

image

 Nest 미들웨어는 기본적으로 express 미들웨어와 동일합니다. 공식 Express 문서에서 미들웨어의 기능을 다음과 같이 설명하고 있습니다.

미들웨어 함수는 다음 작업을 수행할 수 있습니다:

  1. 모든 코드 실행.

  2. request 및 response 객체 변경.

  3. request-response 주기 종료.

  4. 스택에서 다음 미들웨어 함수를 호출.

  5. 현재 미들웨어 함수가 request-response 주기를 종료하지 않으면 next()를 호출하여 다음 미들웨어 함수로 제어를 전달해야 합니다. 그렇지 않으면 요청이 처리되지 않은 상태로 남게 됩니다.


사용자 정의 Nest 미들웨어는 함수 또는 @Injectable() 데코레이터가 있는 클래스에서 구현할 수 있습니다. 클래스는 NestMiddleware 인터페이스를 구현해야 하고, 함수는 특별한 요구사항이 없습니다. 클래스 메서드를 사용하여 간단한 미들웨어 기능을 구현해 봅시다.

// logger.middleware.ts

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

Dependency injection

Nest 미들웨어는 의존성 주입을 지원합니다. 프로바이더와 컨트롤러와 마찬가지로, 동일한 모듈 내에서 사용 가능한 의존성을 주입할 수 있습니다. 이 작업은 평소와 같이 constructor를 통해 수행됩니다.

Applying middleware

미들웨어는 @Module() 데코레이터에는 위치하지 않습니다. 대신 모듈 클래스의 configure() 메서드를 사용하여 설정합니다. 미들웨어가 포함된 모듈은 NestModule 인터페이스를 구현해야 합니다. AppModule 레벨에서 LoggerMiddleware를 설정해 봅시다.

// app.module.ts

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

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

위 예시에서는 이전에 CatsController 내부에 정의된 /cats 라우트 핸들러에 대해 LoggerMiddleware를 설정했습니다. 또한 미들웨어를 특정 요청 방식으로 제한하려면 미들웨어를 설정할 때 forRoutes() 메서드에 라우트 path와 요청 method를 포함한 객체를 전달할 수도 있습니다.

 아래 예시에서, 원하는 요청 방식 유형을 참조하기 위해 RequestMethod 를 가져오는 것을 놓치지 말자.

// app.module.ts

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

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

Route wildcards

패턴 기반 라우트도 지원됩니다. 예를 들어, 별표(*)는 와일드카드로 사용되며, 문자 조합에 대해 모두 일치합니다:

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });

'ab*cd' 루트 경로는 abcd, ab_cd, abecd 등과 일치합니다. ?, +, * 및 ()와 같은 문자들은 라우트 경로에 사용할 수 있으며, 정규 표현식 카운터파트의 부분 집합입니다. 하이픈 (-)과 점(.)은 문자열 기반 경로에서 문자 그대로 해석됩니다.

Middleware consumer

MiddlewareConsumer는 helper 클래스입니다. 미들웨어를 관리하기 위한 여러 가지 내장 메서드를 제공합니다. forRoutes() 메서드는 단일 문자열, 여러 문자열, RouteInfo 객체, 컨트롤러 클래스, 또는 여러 컨트롤러 클래스를 사용할 수 있습니다. 대부분의 경우에 쉼표로 구분된 컨트롤러 목록을 전달할 가능성이 높습니다. 아래는 단일 컨트롤러를 사용한 예시입니다:

// app.module.ts

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

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

Excluding routes

일부 route에 미들웨어를 적용하지 않기위해선 exclude() 메서드를 사용하여 특정 route를 쉽게 제외할 수 있습니다. 이 메서드는 단일 문자열, 여러 문자열 또는 제외되어야 할 루트를 식별하는 RouteInfo 객체를 사용할 수 있습니다. 아래 예시를 확인하세요:

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

위 예시에서, LoggerMiddleware는 CatsController 내에서 정의된 모든 루트에 묶이지만 exclude() 메서드에 전달된 세 개의 루트를 제외합니다.

Functional middleware

미들웨어를 왜 클래스 대신 간단한 함수에서 정의하지 않을까요? 실제로 가능합니다.

이런 종류의 미들웨어를 함수형 미들웨어라고 합니다.

 미들웨어에 종속성이 필요하지 않은 경우, 간단한 함수형 미들웨어 대안을 사용하는 것이 좋습니다. 함수형 미들웨어는 종속성이 없고 코드가 간결하며, 더 쉽게 관리할 수 있는 이점이 있습니다.

// logger.middleware.ts

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

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

그리고 AppModule 내에서 사용합니다:

// app.module.ts

consumer
  .apply(logger)
  .forRoutes(CatsController);

Multiple middleware

위에서 언급한 바와 같이 순차적으로 실행되는 여러 미들웨어를 묶으려면, apply() 메서드 내에서 쉼표로 구분된 목록을 제공하면 됩니다:

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

Global middleware

한 번에 모든 등록된 루트에 미들웨어를 묶고 싶다면, INestApplication 인스턴스에서 제공하는 use() 메서드를 사용할 수 있습니다:

// main.ts

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

전역 미들웨어에서 DI 컨테이너에 접근할 수 없습니다. app.use()를 사용할 때 함수형 미들웨어를 대신 사용할 수 있습니다. 또는 클래스 미들웨어를 사용하고 AppModule (또는 다른 모듈) 내에서 .forRoutes('*')로 사용할 수 있습니다.