
라우트 핸들러, 즉 컨트롤러 호출 이전에 실행되는 일종의 "방화벽"같은 개념이다.
Request와 Response 객체에 접근 가능하고, next() 미들웨어 함수로 다음 미들웨어를 호출한다(?).
검색해도 next()와 관련된거는 안나오고 NextJS로만 나오더라...
미들웨어 함수는 다음 동작을 실행할 수 있는데,
next() 함수를 사용해야한다. 그렇지 않으면 요청이 보류된다. (정확히는 미들웨어 체인을 마무리 짓는다.)@Injectable() 데코레이터 사용NestMiddleware 인터페이스 구현(상속받아 함수 재정의)공식 예제에서는 미들웨어를 src/common/middleware/ 경로에 저장하였다.
import { NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
// Injectable 데코레이터 사용과 NestMiddleware 구현
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
이후 app.module.ts에 다음과 같이 적어주자.
import { MiddlewareConsumer, Module, NestMiddleware, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
@Module({
imports: [CatsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule { // NestModule을 구현하겠다!
configure( consumer: MiddlewareConsumer) { // consumer를 MiddlewareConsumer로 설정
consumer
.apply(LoggerMiddleware) // 이 middleware를 사용할건데,
.forRoutes('cats*') // 이 경로에 대해서만 해줘(와일드카드 사용 가능)
// .forRoutes({ path: string, method: RequestMethod}) // path 경로로 이 method로 요청을 할때만 실행해줘
}
}
여기서 일일히 forRoutes에 경로를 지정할 필요 없이 Controller 자체를 넣어도 된다. 즉,
consumer
.apply(LoggerMiddleware)
.forRoutes(CatsController);
로도 가능하다는 얘기!
만약 여기서 CatsController에 있는 경로 이외의 것들도 포함하고 싶다면
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)', // cats/*의 모든 메소드를 받겠다
)
.forRoutes(CatsController);
로도 가능하다.
인증, 인가 미들웨어를 만들어놨는데 특정 컨트롤러의 엔드포인드만 필요한 경우 exclude를 이용하여 컨트롤러 전체를 라우팅할 필요가 없어진다.
전역 미들웨어로 사용하고싶다면 main.ts에 app.use로 미들웨어를 추가해주자.
이때는 클래스로 작성하면 안되고 함수형으로 작성해야한다. 아니면 app.module.ts에서 .forRoutes('*')으로 쓰든지.
// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
미들웨어는 중첩시 어떻게 동작할까? 테스트를 해보자.
export function test1Middleware(req: Request, res: Response, next: NextFunction) {
console.log('testing middleware... 1');
next();
console.log('testing middleware... 1 end');
}
export class Test2Middleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('testing middleware... 2');
next();
console.log('testing middleware... 2 end');
}
}
export class AppModule implements NestModule {
configure( consumer: MiddlewareConsumer) {
consumer
.apply(test1Middleware, Test2Middleware) // 여러개의 미들웨어를 한 번에 가능하게 하는 마법
.forRoutes('cats')
}
}
// 아래의 AppModule도 같은 결과를 출력한다.
// 어차피 chain이 걸리기 때문
// export class AppModule implements NestModule {
// configure( consumer: MiddlewareConsumer) {
// consumer
// .apply(test1Middleware)
// .forRoutes('cats')
// .apply(Test2Middleware)
// .forRoutes('cats')
// }
// }
결과는 아래와 같다.

스택 구조로 쌓이는 것이다!
만약 Test1Middleware에서 next()를 없앤다면?

스택이 쌓이지 않는다.
정확히는 스택이 있긴 한데 다음으로 넘기지 않는 것.
export interface NestMiddleware<TRequest = any, TResponse = any> {
use(req: TRequest, res: TResponse, next: (error?: Error | any) => void): any;
}
export interface NestModule {
configure(consumer: MiddlewareConsumer): any;
// 비동기 작업이 가능하다!
// async/await가 가능하다는 말
}