Middleware

이연중·2021년 7월 27일
0

NestJS

목록 보기
5/22

미들웨어에 대한 설명에 앞서 다음 Nest의 요청 생명주기에 대해 알고 가는 것이 도움이 될 것 같다.

Nest Request Lifecycle

  1. Incoming request
  2. Globally bound middleware
  3. Module bound middleware
  4. Global guards
  5. Controller guards
  6. Route guards
  7. Global interceptors (pre-controller)
  8. Controller interceptors (pre-controller)
  9. Route interceptors (pre-controller)
  10. Global pipes
  11. Controller pipes
  12. Route pipes
  13. Route parameter pipes
  14. Controller (method handler)
  15. Service (if exists)
  16. Route interceptor (post-request)
  17. Controller interceptor (post-request)
  18. Global interceptor (post-request)
  19. Exception filters (route, then controller, then global)
  20. Server response

다음과 같은 과정을 거쳐 요청에 대한 처리가 되며, 응답이 전송된다.

미들웨어는 Route Handler(메서드)실행 이전에 호출되는 함수이다.

미들웨어는 요청 및 응답 객체에 엑세스할 수 있으며, 어플리케이션의 요청-응답 주기에 있는 next() 미들웨어 함수에 엑세스할 수 있다.

Nest의 미들웨어는 기본적으로 express의 미들웨어와 동일하다.

함수 또는 @Injectable() 데코레이터가 있는 클래스에서 Custom Nest Middleware를 구현할 수 있다. 클래스로 구현할 경우 NestMiddleware 인터페이스를 implement해야 하지만 함수로 구현하는 경우에는 특별히 작성해야 되는 것이 없다.

다음은 클래스로 커스텀하게 미들웨어를 구현한 예이다.

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


Nest 미들웨어는 dependency injection을 완벽히 지원한다.

Provider와 Controller와 마찬가지로 동일 모듈내에서 사용할 수 있는 종속성을 주입할 수 있다.(constructor를 통해)

Applying Middleware


모듈의 @Module() 데코레이터에는 미들웨어를 따로 등록할 수 있는 곳이 없다.

대신, NestModule 클래스의 configure() 메서드를 사용해 이를 등록할 수 있다.

미들웨어를 포함하는 모듈은 다음과 같이 NestModule 인터페이스를 implement해야 한다.

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

처음에 정의한 LoggerMiddlewareCatsController 내부에 정의된 /cats route handler에 대해 실행하도록 설정했다.

미들웨어를 구성할 때 아래와 같이 path로 경로와 요청 method가 포함된 객체를 forRoutes() 메서드에 전달해 기준을 둘 수도 있다.

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() 메서드는 async/await를 사용할 수도 있다.
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET /*여기*/});
  }
}

Route wildcard


패턴 기반의 route도 사용할 수 있다.

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

*를 적용하면, abcd, ab_cd, abecd 등의 route path들과 일치한다.

?, +, *, () 문자는 route path로 사용될 수 있으며, -, .은 문자 그대로 해석된다.

Middleware Consumer


MiddlewareConsumer는 Helper 클래스이다. 미들웨어를 관리하기 위해 몇가지 내장된 방법을 제공한다.

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

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware) 
      //apply()에는 미들웨어 하나만 넣을 수도 있고, 여러 개를 넣을 수도 있다.
      //여러 개 넣는 경우
      //consumer.apply(cors(), helmet(), logger).forRoutes(CatsController)
      .forRoutes(CatsController);
  }
}

Excluding Routes


미들웨어를 등록할 때 특정 라우트를 제외할 경우가 있다.

exclude() 메서드로 특정 라우트를 쉽게 제외할 수가 있다.

exclude() 메서드는 path-to-regexp 패키지를 사용해 와일드 카드 매개 변수를 지원한다.

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

Functional Middleware


여태까지의 middleware의 예는 클래스로 정의한 미들웨어였다. 아까도 말했듯, 함수로도 미들웨어를 정의할 수 있다고 했다.

함수로 정의한 미들웨어를 Functional Middleware라고 한다.

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

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

아까의 클래스로 정의한 LoggerMiddleware를 함수로 변화한 예이다.

그리고 이를 모듈에 등록해보겠다.

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

미들웨어에 대한 Dependency Injection이 필요하지 않은 경우에 Functional Middleware 사용

Global Middleware


미들웨어를 전역으로 등록하고 싶으면, INestApplication 인스턴스에서 제공하는 use() 메서드를 사용하면 된다.

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

이 경우 해당 미들웨어를 DI 하는 것은 불가능하다. 전역으로 미들웨어를 등록하고, DI까지 원한다면, functional middleware를 등록하거나 AppModule이나 다른 모듈에 .forRoutes('*')로 등록하면 된다.

참고

https://docs.nestjs.kr/middleware

profile
Always's Archives

0개의 댓글