미들웨어에 대한 설명에 앞서 다음 Nest의 요청 생명주기에 대해 알고 가는 것이 도움이 될 것 같다.
Nest Request Lifecycle
다음과 같은 과정을 거쳐 요청에 대한 처리가 되며, 응답이 전송된다.
미들웨어는 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();
}
}
Nest 미들웨어는 dependency injection을 완벽히 지원한다.
Provider와 Controller와 마찬가지로 동일 모듈내에서 사용할 수 있는 종속성을 주입할 수 있다.(constructor
를 통해)
모듈의 @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');
}
}
처음에 정의한 LoggerMiddleware
를 CatsController
내부에 정의된 /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도 사용할 수 있다.
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
*
를 적용하면, abcd
, ab_cd
, abecd
등의 route path들과 일치한다.
?
, +
, *
, ()
문자는 route path로 사용될 수 있으며, -
, .
은 문자 그대로 해석된다.
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);
}
}
미들웨어를 등록할 때 특정 라우트를 제외할 경우가 있다.
exclude()
메서드로 특정 라우트를 쉽게 제외할 수가 있다.
exclude()
메서드는 path-to-regexp
패키지를 사용해 와일드 카드 매개 변수를 지원한다.
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
여태까지의 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 사용
미들웨어를 전역으로 등록하고 싶으면, INestApplication
인스턴스에서 제공하는 use()
메서드를 사용하면 된다.
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
이 경우 해당 미들웨어를 DI 하는 것은 불가능하다. 전역으로 미들웨어를 등록하고, DI까지 원한다면, functional middleware를 등록하거나
AppModule이나 다른 모듈에
.forRoutes('*')로 등록하면 된다.