Skip to content

5. Middleware


미들웨어는 라우터 핸들러 전에 호출되는 함수이다. 미들웨어 함수는 요청 및 응답 객체에 액세스할 수 있으며 애플리케이션의 요청/응답 주기에 있는 next() 미들웨어 함수에 액세스할 수 있다. next 미들웨어 함수는 일반적으로 next라는 변수로 표시된다.


001


Nest 미들웨어는 기본적으로 Express 미들웨어와 동일하다. 다음은 미들웨어의 기능에 대해 설명한다:


  • 모든 코드를 실행한다.
  • 요청 및 응답 객체를 변경한다.
  • 요청/응답 주기를 종료한다.
  • 스택의 다음 미들웨어 함수를 호출한다.
  • 현재 미들웨어 함수가 요청/응답 주기를 종료하지 않으면 next()를 호출하여 다음 미들웨어 함수에 제어를 전달해야 하며, 그렇지 않으면 요청이 중단된다.


함수 또는 @Injectable() 데코레이터가 있는 클래스에서 사용자 정의 Nest 미들웨어를 구현한다. 클래스는 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();
  }
}


1. Dependency injection

Nest 미들웨어는 종속성 주입을 완벽하게 지원한다. 프로바이더 및 컨트롤러와 마찬가지로 동일한 모듈 내에서 사용 가능한 종속성을 주입할 수 있다.


2. 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() 메서드에 전달하여 미들웨어를 특정 요청 메서드로 제한할 수도 있다. 아래 예제에서 원하는 요청 메서드 타입을 참조하기 위해 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 });
  }
}


Note

configure() 메서드는 async/await를 사용하여 비동기로 만들 수 있다.


3. Route wildcards

패턴 기반 라우트도 지원된다. 예를 들어 애스터리스크는 와일드카드로 사용되며 모든 문자 조합과 일치한다.


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


'ab*cd' 라우트 경로는 abcd, ab_cd, abecd 등과 일치한다. ?, +, *, () 문자는 라우트 경로에 사용될 수 있다.


4. Middleware consumer

MiddlewareConsumer는 헬퍼 클래스이다. 미들웨어를 관리하는 몇 가지 기본 메서드를 제공한다. 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);
  }
}


Note

apply() 메서드는 단일 미들웨어뿐만 아니라 여러 미들웨어도 지정할 수 있다.


5. Excluding routes

때로는 미들웨어가 적용되는 특정 라우트를 제외해야 할 때가 있다. exclude() 메서드를 사용하여 특정 라우트를 쉽게 제외할 수 있다. 이 메서드는 아래와 같이 제외할 라우트를 식별하는 단일 문자열, 여러 문자열, RouteInfo 객체를 사용할 수 있다.


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


위의 예제에서 LoggerMiddlewareexclude() 메서드에 전달된 세 개를 제외하고 CatsController 내부에 정의된 모든 라우트에 바인딩된다.


6. Functional middleware

위의 예제의 LoggerMiddleware 클래스는 멤버, 추가 메서드 및 종속성이 없는 간단한 구조이다. 이런 경우 클래스를 함수로 변환하면 된다. 차이점을 설명하기 위해 로거 미들웨어를 클래스 기반에서 함수 기반으로 변환해 보자.


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


Note

미들웨어에 종속성이 필요하지 않은 경우 함수 기반 미들웨어를 사용하는 것이 좋다.


7. Multiple middleware

위에서 언급했듯이 순차적으로 실행되는 여러 미들웨어를 바인딩하려면 apply() 메서드 내부에 쉼표로 구분된 목록을 제공하기만 하면 된다.


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


8. Global middleware

미들웨어를 등록된 모든 라우트에 한 번에 바인딩하려면 INestApplication 인스턴스에서 제공하는 use() 메서드를 사용하면 된다.


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


Note

글로벌 미들웨어에서 종속성 주입 컨테이너에 액세스하는 것은 불가능하다. 이런 경우 클래스 미들웨어를 사용하고 AppModule(또는 다른 모듈) 내에서 .forRoutes('*')를 사용한다.


References