새소식

반응형
Back-end/NestJS

[NestJS | Docs] Middleware 알아보기

2024.02.28
  • -
반응형

1. Middleware

미들웨어는 라우트 핸들러 전에 호출되는 함수입니다. 미들웨어 함수는 애플리케이션의 request-response 사이클에서 request, response 객체, 그리고 next() 미들웨어 함수에 대한 접근을 갖습니다.

 

next 미들웨어 함수는 next라는 이름을 가진 변수로 흔히 표기됩니다.

 

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

 

미들웨어 함수는 다음과 같은 작업들을 수행할 수 있다:
- 어떠한 코드든 실행한다.
- request와 response 객체를 수정한다.
- request-response 사이클을 종료시킨다.
- 스택에서 다음 미들웨어 함수를 호출한다.
- 만약 가장 최근에 호출된 미들웨어 함수에서 request-response 사이클을 종료시키지 않았다면, 해당 미들웨어는 반드시 next()를 호출하여 다음 미들웨어 함수에게 제어권을 넘겨야 한다.
(그렇지 않으면 request는 해결되지 않은 채로 남아있을 것이다.)

 

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

 

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-1. Dependency Injection

Nest 미들웨어는 의존성 주입에 대해 완전한 지원을 하고 있습니다. provider 및 controller와 마찬가지로 동일한 모듈 내에서 사용할 수 있는 의존성을 주입할 수 있습니다. 이 작업은 일반적인 경우들과 마찬가지로 생성자를 통해 수행됩니다.

 

1-2. Applying middleware

@Module() 데코레이터는 미들웨어를 위한 자리가 존재하지 않습니다. 대신 모듈 클래스의 configure() 메서드를 사용하여 설정할 수 있습니다. 미들웨어를 포함하는 모듈은 NestModule 인터페이스를 구현해야 합니다. AppModule 수준에서 LoggerMiddleware를 설정해 보겠습니다.

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 열거형을 가져온 것을 볼 수 있습니다.

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

 

config() 메서드는 async/await을 사용하여 비동기화할 수 있습니다(예: config() 메서드 본문 내에서 비동기 연산이 완료될 때까지 기다릴 수 있음).

 

WARNING
express 어댑터를 사용할 때 NestJS 앱은 기본적으로 패키지 body-parser에서 json 및 urlencoded를 등록합니다. 즉, MiddlewareConsumer를 통해 해당 미들웨어를 커스텀하려면 NestFactory.create()로 애플리케이션을 생성할 때 bodyParser 플래그를 false로 설정하여 전역 미들웨어를 꺼야 합니다.

 

1-3. Route wildcards

패턴 기반 경로도 지원됩니다. 예를 들어 별표(*)는 와일드카드로 사용되며 어떤 문자 조합과도 일치합니다:

 

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

 

'ab*cd' 경로 경로는 abcd, ab_cd, abecd 등과 일치합니다. 문자 ?, +, *, ()는 라우트 경로에 사용할 수 있으며 정규식 대응 문자의 하위 집합입니다. 하이픈( - )과 점( . )은 문자열 기반 경로에서 문자 그대로 해석됩니다.

 

1-4. Middlware consumer

MiddlewareConsumer는 미들웨어를 관리하기 위한 몇 가지 내장 메서드를 제공하는 헬퍼 클래스입니다. 해당 클래스의 모든 메서드는 fluent style로 간단히 연결할 수 있습니다.

 

forRoutes() 메서드는 단일 문자열, 여러 문자열, RouteInfo 객체, 컨트롤러 클래스, 심지어 여러 컨트롤러 클래스까지도 받을 수 있습니다. 대부분의 경우 쉼표로 구분된 컨트롤러 목록을 전달할 것입니다. 아래는 단일 컨트롤러를 사용한 예제입니다:

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() 메서드는 단일 미들웨어를 받거나 여러 미들웨어를 지정하기 위해 여러 인수를 받을 수 있습니다.

 

1-5. Excluding routes

특정 라우트를 미들웨어 적용 대상에서 제외하고 싶을 때가 있을 수 있습니다. 이 때는 exclude() 메서드를 사용하면 특정 경로를 쉽게 제외할 수 있습니다. 이 메서드는 아래와 같이 제외할 라우트를 식별하는 단일 문자열, 여러 문자열 또는 RouteInfo 객체를 받을 수 있습니다:

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

 

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

 

1-6. Functional middleware

우리가 사용한 LoggerMiddleware 클래스는 매우 간단합니다. 멤버도 없고, 추가 메서드도 없으며, 의존성도 없습니다.

 

클래스 대신 간단한 함수로 정의할 수 없는 이유는 무엇일까요? 사실은 가능합니다. 아래 유형의 미들웨어를 함수형 미들웨어라고 합니다. 차이점을 설명하기 위해 로거 미들웨어를 클래스 기반에서 함수형 미들웨어로 변환해 보겠습니다:

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

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

 

그리고 이를 AppModule내에서 사용하도록 합니다.

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

 

1-7. Multiple middleware

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

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

 

 

1-8. Global middlware

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

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

 

참고로 글로벌 미들웨어에서는 DI 컨테이너에 액세스할 수 없습니다.

 

app.use()를 사용할 때 함수형 미들웨어를 대신 사용할 수 있습니다. 또 다른 방법은, 클래스 미들웨어를 사용하고 AppModule(또는 다른 모듈) 내에서 .forRoutes('*')를 사용하여 사용하는 것입니다.

 

 

 

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.