5. Middleware

미들웨어는 라우트 핸들러 이전에 호출되는 기능이다 . 미들웨어 기능은 요청  응답 개체에 액세스할 수 있으며 요청-응답 주기에 있다 next() 함수를 통해 다음에 따라오는 미들웨어 호출이 이루어진다.

Nest 미들웨어는 기본적으로 Express 미들웨어와 동일하다. 공식 Express 문서는 미들웨어를 다음과 같이 설명한다.

미들웨어 기능은 다음 작업을 수행할 수 있습니다.
  • 어떤 코드라도 실행.
  • 요청 및 응답 개체를 변경.
  • 요청-응답 주기를 종료.
  • 스택에서 다음 미들웨어 함수를 호출.
  • 현재 미들웨어 기능이 요청-응답 주기를 종료하지 않으면 next()다음 미들웨어 기능으로 제어를 전달하기 위해 호출한다. 그렇지 않으면 요청이 중단된 상태로 유지된다.

함수나 @Injectable() 데코레이터가 있는 클래스에서 맞춤 Nest 미들웨어를 구현한다. 클래스는 NestMiddleware인터페이스를 구현해야 하며 함수에는 특별한 요구 사항이 없다. 클래스 메소드를 사용하여 간단한 미들웨어 기능을 구현하는 것부터 시작해보자.

 

주의) 

Express 와 fastify는 미들웨어를 다르게 처리한다. 자세한 내용은 여기를 참고한다.

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

 

의존성 주입

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

 

미들웨어 적용

@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 를 설정하였다. 또한 미들웨어를 구성할 때 라우트 path와 요청이 포함된 개체를 메서드에  forRoutes()  메소드에 전달하여 미들웨어를 특정 request의 메서드(get, put, post, delete..)로  제한할 수도 있다. 아래 예에서는 원하는 요청 메서드 유형을 참조하기 위해 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 });
  }
}

configure 메서드는 async/await 을 사용하여 비동기 메서드가 될 수 있다.

 

주의) express 어댑터를 사용하는 경우, Nest 는 기본적으로 body-parser 패키지의 json과 urlencoded를 내장하게 된다.  MiddlewareConsumer를 통해 bodyParser를 커스텀해야 하는 경우 글로벌 미들웨어에서 bodyParser 플래그를 false로 꺼야 한다.

 

와일드카드 라우팅

패턴 기반 경로도 지원된다. 예를 들어 별표는 와일드카드 로 사용되며 모든 문자 조합과 일치한다.

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

'ab*cd'  경로는 abcd, ab_cd, abecd 등과 일치합니다 . ?, +, *   () 문자는 라우팅 경로에 사용될 수 있으며 정규식과 유사하다. 다만, 하이픈( - )과 점( . )은 문자열 기반 경로에 의해 문자 그대로 해석된다.

 

주의) fastify  패키지는 더 이상 와일드카드 별표를 지원하지 않는 최신 버전의  path-to-regexp 패키지를 사용합니다 . 대신 (.*), :splat* 파라미터를 사용해야 한다.

 

미들웨어 소비자(Middleware Consumer)

MiddlewareConsumer 는 헬퍼 클래스이다 . 미들웨어를 관리하기 위한 몇 가지 기본 제공 방법을 제공한다. 이들 모두는 유창한 스타일(fluent style) 로 간단하게 연결될 수 있다 . 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);
  }
}

힌트) apply()메서드는 단일 미들웨어를 사용하거나 여러 미들웨어를 지정하기 위해 여러 인수를 사용할 수 있습니다 .

 

라우팅 제외

때때로 우리는 미들웨어가 적용되지 않도록 특정 경로를 제외하고 싶을 때가 있다.  exclude() 메서드를 호출하는 방법으로 특정 경로를 쉽게 제외할 수 있다. 이 메서드는 아래와 같이 단일 문자열, 여러 문자열 또는 제외할 경로를 식별하는 RouteInfo 개체를 사용할 수 있다.

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

힌트) exclude()  서드는 path-to-regexp패키지를 사용하여 와일드카드 매개변수를 지원합니다 .

 

위의 예에서는 exclude() 메서드에 전달된 세 가지 경로를 제외하고 LoggerMiddleware 내부에 정의된 CatsController의 모든 경로에 바인딩된다.

 

기능적 미들웨어

우리가 사용한 클래스는 LoggerMiddleware 매우 간단합니다 . 여기에는 멤버, 추가 메서드 및 종속성이 없습니다. 그렇다면, 왜 클래스 대신 간단한 함수로 정의할 수 없을까? 라는 의문이 생긴다, 결론적으로 당연히 된다. 이렇게 간단하게 함수로 정의한 유형의 미들웨어를 기능적 미들웨어 라고 한다 . 차이점을 설명하기 위해 LoggerMiddleware 를 클래스 기반에서 기능적 미들웨어로 변환해 보겠다.

// logger.middleware.ts

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

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

루트 모듈에 등록하자.

// app.module.ts

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

힌트) 미들웨어에 종속성이 필요하지 않은 경우에는 언제든지 더 간단한 기능적 미들웨어 대안을 사용하는 것을 고려해보자.

 

다중 미들웨어

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

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

 

글로벌 미들웨어

등록된 모든 경로에 미들웨어를 한 번에 바인딩하려면 INestApplication 인스턴스에서 제공하는 use() 메서드를 사용할 수 있습니다.

// main.ts

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

힌트) 글로벌 미들웨어에서는 DI 컨테이너에 접근할 수 없다. 그리고, app.use() 를 사용할 때 기능적 미들웨어를 대신 사용할 수 있다.

'Backend(Framework) > NestJS 개요(공식문서 번역)' 카테고리의 다른 글

7. Pipes  (0) 2023.11.19
6. Exception filters  (0) 2023.11.17
4. Modules  (1) 2023.11.16
3. Provider  (1) 2023.11.16
2. Controller  (0) 2023.11.05
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유