NestJS Logo

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

Table of Contents

  1. Middleware

Middleware

미들웨어는 라우트 핸들러 전에 호출되는 함수입니다. 미들웨어 함수는 requestresponse 오브젝트에 접근 권한을 가집니다. 그리고 애플리케이션의 request-response 사이클 안에서 next() 미들웨어 함수도 가집니다.

middlewares

Image by: https://docs.nestjs.com/assets/Middlewares_1.png

Nest 미들웨어는 디폴트로 express 미들웨어와 동일하다. express 공식 문서에서 기술하는 미들웨어의 기능을 따른다.

미들웨어 함수는 아래와 같은 일을 수행할 수 있다.

  • 어떠한 코드든 실행할 수 있다.
  • request, response 객체를 바꿀 수 있다.
  • request-response cycle을 끝낼 수 있다.
  • stack 안에서 next 미들웨어 함수를 호출할 수 있다.
  • 만약 현재 미들웨어 함수가 request-response cycle을 종료하지 않는다면, 다음 미들웨어 함수에게 제어권을 패스하기 위해 next()를 반드시 호출해야 한다. 그렇지 않으면, 그 Request는 영영 갇히게 된다.

커스텀 NestJS Middleware는 함수나 @Injectable() 데코레이터를 붙인 클래스를 구현해야 한다. class는 반드시 NestMiddleare 인터페이스를 구현해야 하고, function을 사용하면 특별히 해야할 건 없다. 아래 코드를 보면서 간단한 Middleware를 보자.

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

NestJS Middleware는 의존성 주입을 완벽하게 지원한다. Provider, Controller와 마찬가지로 같은 모듈 안에서 의존성 주입이 가능하다. 늘 그렇듯이 의존성은 contructor를 통해 주입된다.

Applying middleware

@Module() 데코레이터에는 middleware를 넣을 공간이 없다. 대신, 모듈 클래스에 configure() 메서드를 사용함으로써 설정할 수 있다. Module 클래스는 NestModule 인터페이스를 구현해야 한다. 아래 예제를 보자.

// 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');
  }
}

위 예시는 LoggerMiddleware/cats 라우트 핸들러에 설정했다. 만약 request method를 제한하고 싶다면 forRoutes() 메서드에 객체로 pathmethod 프로퍼티를 설정하면 된다. 아래 보이는 예시에서는 우리가 제한하고자 하는 request method 타입을 RequestMethod enum으로 import해서 참조 했다.

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

configure() 메서드에서 async/await을 사용해서 비동기 함수로 만들 수도 있다.

Route wildcards

패턴 base 라우트도 잘 지원한다. 예를들어 asterisk(*)는 wildcard로 사용될 수 있다. 그리고 어떤 문자의 조합이든 일치할 수 있다.

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

매칭 되는 문자열은 여기서 설명한 것과 똑같다.

+) fastify 패키지는 path-to-regexp 패키지의 마지막 버젼을 사용해서 * 가 지원되지 않는다고 한다. 대신, (.*), :splat*을 사용하면 된다고 한다.

Middleware consumer

MiddleWareConsumer는 helper 클래스다. 이는 미들웨어를 관리하기 위한 built-in(내장된) 메서드를 제공한다. 메서드의 모든것들은 fluent style처럼 chain 형태가 될 수 있다. forRoutes() 메서드는 single string, multiple strings, RouteInfo object, controller, 심지어 다수의 컨트롤러를 가질 수 있다. 대다수의 경우, commas(`)로 구분해 컨트롤러의 리스트를 전달할 것이다. 아래는 싱글 컨트롤러를 사용한 예다.

// 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.ts';

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

apply() 메서드는 하나의 미들웨어 또는 여기에 나온 것처럼 여러 인자들을 가질 수 있다.

Excluding routes

때로는 특정 라우트에 미들웨어를 적용하지 않기를 원한다. 우리는 exclude() 메서드를 사용하여 쉽게 특정 라우트를 제외할 수 있다. 이 메서드는 하나의 문자열 또는 다수의 문자열 또는 제외할 라우트를 식별할 RouteInfo 객체를 가질 수 있다. 아래와 같다.

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

exclude() 메서드는 path-to-regexp 패키지를 사용한 와일드카드 파라미터를 지원한다.

위 예제 코드에 따르면, LoggerMiddlewareexclude() 메서드에 전달한 3가지 사황을 제외한 CatsController의 모든 라우트에 적용된다.

Functional middleware

위에서 본 LoggerMiddleware 클래스는 매우 심플하다. 아무 member도 가지지 않고, 추가 메서드도 없고 의존성도 없다. 이럴땐 간단한 함수로 클래스를 대신할 수 있다. 이런 종류의 미들웨어를 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);

사용하는 미들웨어가 어느 의존성도 필요하지 않는다면 간단한
functional middleware를 사용하는 대안도 고민해보자.

Multiple middleware

위에서 언급한 것처럼, 연속적으로 동작하는 다수의 미들웨어를 등록하기 위해 apply() 메서드 안에 comma(,)로 분리된 리스트를 제공하면 된다.

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

Global middleware

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

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
Next NestJS OVERVIEW(6) -
Intro NestJS OVERVIEW(0) - Intro