처음부터 차근차근

[NestJS] Middleware 본문

FrameWork/NestJS

[NestJS] Middleware

HangJu_95 2023. 11. 25. 14:00
728x90

Middleware

미들웨어는 클라이언트로 들어온 요청을 각 컨트롤러의 요청 핸들러가 처리하기 이전에 코드를 실행할 수 있는 기능을 의미합니다.

NestJS 요청 생명주기에서 클라이언트로부터 들어오는 request가 가장 먼저 처리되는 핸들러입니다.

 

Express에서 많이 다루던 분이면 알겠지만, Request와 Response의 중간에 위치하여 Middleware라고 불리며, Middleware는 요청과 응답 객체를 수정할 수 있습니다.

그리고 next()라는 메서드를 호출하여 그 다음 미들웨어 혹은 라우터가 작업을 처리할 수 있도록 하거나, 혹은 Request를 보류할 수도 있습니다.

NestJS Middleware

Nest의 Middleware는 기본적으로 Express와 동일하며 다음과 같은 기능을 수행합니다.

  • 어떠한 코드를 살행할 수 있습니다.
  • 요청 및 응답 개체를 변경할 수 있습니다.
  • 요청-응답 주기를 종료합니다.
  • 스택의 다음 미들웨어 기능을 호출합니다.
  • 현재 미들웨어 기능이 요청-응답 주기를 종료하지 않는 경우, 다음 미들웨어 기능으로 제어를 전달하기 위해 next()를 호출해야 합니다. 그렇지 않으면 요청이 보류(hang)됩니다.

미들웨어를 구현하는 방법은 크게 두가지 방법이 있습니다.

  1. @Injectable() 데코레이터가 있는 클래스로 구현한다.
  2. 하나의 함수형 미들웨어를 구현한다.

간단한 예시로 클래스를 통해 Logger 미들웨어를 구축해보겠습니다.

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

클래스로 미들웨어를 구현하려면 NestMiddleware 인터페이스를 통해 구현해야 합니다. 

의존성 주입

Nest Middleware는 의존성 주입을 완벽하게 지원합니다.

프로바이더 및 컨트롤러와 마찬가지로 동일한 모듈 내에서 사용할 수 있는 의존성을 주입할 수 있습니다.

Middleware 적용

@Module() 데코레이터를 설정하는 속성에는 Middleware를 설정하기 위한 속성은 존재하지 않습니다.

대신 모듈 클래스의 configure() 메서드를 사용하여 설정할 수 있습니다.

// 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() 메서드를 통해 미들웨어 적용
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('cats');
  }
}

미들웨어를 포함하는 모듈 Class는 NestModule 인터페이스를 구현해야 합니다.

현재는 /cats 경로의 Resource를 요청할 경우 LoggerMiddleware를 적용하도록 설정했지만, 특정 경로의 특정 메서드에만 적용하고 싶은 경우 아래와 같이 코드를 작성할 수 있습니다.

// 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) {
  	// paht와 method를 설정하는 모습
    consumer.apply(LoggerMiddleware).forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

Route wildcards

패턴 기반 Routing 역시 지원한다.

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

 

NestJS에서 지원하는 패턴 기반 라우팅은 정규표현식의 부분집합, 즉, 모든 정규표현식의 기능을 지원하지는 않습니다.

라우팅 경로에서 문자 ?, + , * 및 () 를 사용할 수 있으며, 하이픈 및 점은 문자 그대로 문자열 기반으로 해석됩니다.

Middleware consumer

Nest가 제공하는 MiddlewareConsumer는 Helper Class이며, 미들웨어를 잘 사용하기 위해 유용한 기능을 제공합니다.

Method Chaning으로 설정하며, forRoutes 부분은 단순 문자열, 여러 개의 문자열, RouteInfo 객체, Controller 클래스를 받을 수 있습니다.

apply Method에서도 여러가지의 미들웨어를 받을 수 있습니다.

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

경로 제외

exclude() 메서드를 사용하면 Controller의 특정 path만 쉽게 제외할 수 있습니다.

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

마찬가지로 단일 문자열, 다중 문자열 또는 RouteInfo 객체를 사용할 수 있습니다.

exclude()메서드는 path-to-regexp 패키지를 사용하여 와일드카드를 지원합니다.

함수형 미들웨어

만약 내가 만들 미들웨어가 정말로 간단하다면(종속성도 없고, 메서드도 없고..), 간단한 함수형 미들웨어를 작성하는 것도 한가지 방법입니다. 방법은 Express의 미들웨어 함수 작성 방법과 동일합니다.

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

전역 미들웨어

미들웨어를 등록된 모든 경로에 한 번에 바인딩하려면 use() 메서드를 사용하면 된다.

const app = await NestFactory.create(AppModule);
// Express와 동일한 사용방법
app.use(logger);
await app.listen(3000);

💡Nest에서는 다양한 기능을 지원하는데 꼭 Middleware를 사용해야 하나요?

NestJS에는 Guard, interceptor등 다양한 기능을 지원합니다. 따라서 Middleware를 꼭 사용할 필요는 없습니다.

하지만 Guard 대신 Middleware를 사용하는 방식 등 이러한 방법으로 middleware를 사용하는 방법은 지양하는데요, 그 이유는 다음과 같습니다.

Middleware는 파라미터로 request, response, next 이 세 가지를 받는데요, NestJS에서 request와 response가 HTTP 위에서 동작하게 설계되어 있기 때문에 HTTP 통신이 아니면 사용이 불가합니다. 반면에 Interceptor는 파라미터로 execution context라는 helper class를 받아 처리하기 때문에 HTTP 이외에도 WebSocket, GraphQL, RPC(Remote procedure call)위에서도 동작 가능합니다.

 

이 외에도 Middleware, Guards, Interceptors, Pipes, Filters는 기술적으로 모두 NodeJS에서 말하는 Middleware에 속하지만, NestJS에선 Guards, Interceptors, Pipes, Filters를 enhancer라고 부르며, 꼭 Middleware가 필요한 경우가 아니라면 enhancer 쓰길 권장하고 있습니다.

NestJS 디스코드 Trilon 직원의 답변

참고

https://docs.nestjs.com/middleware

https://one-armed-boy.tistory.com/entry/NestJS-Request-Lifecycle-1-Middleware?category=1105866

https://dkrnfls.tistory.com/83

https://gongmeda.tistory.com/53

https://blog-ko.superb-ai.com/nestjs-interceptor-and-lifecycle/

 

 

'FrameWork > NestJS' 카테고리의 다른 글

[NestJS] interceptor  (0) 2023.11.27
[NestJS] Guard  (0) 2023.11.27
[NestJS] Request Lifecycle  (0) 2023.11.25
[NestJS] Module  (1) 2023.11.24
[NestJS] Providers  (0) 2023.11.24