nest의 공식문서를 토대로 작성합니다.
미들웨어는 route handler 전에 호출되는 함수입니다. 미들웨어 함수는 애플리케이션의 요청-응답 주기에서 요청, 응답 객체와 next() 미들웨어 함수에 접근할 수 있습니다. next
미들웨어 함수는 일반적으로 next라는 변수로 표시됩니다.
기본적으로 Nest 미들웨어는 express 미들웨어와 동일합니다. 공식 express 문서의 다음 설명은 미들웨어의 기능에 대해 설명합니다.
미들웨어 함수가 수행할 수 있는 작업
- 코드를 실행
- 요청 및 응답 객체를 변경
- 요청-응답 사이클 종료
- 스택에서 다음 미들웨어 함수를 호출
- 현재 미들웨어 함수가 요청-응답 사이클을 종료하지 않으면 다음 미들웨어 함수에 제어권을 넘기기 위해 next()를 호출해야 함. 그렇지 않으면 요청이 중단된 상태로 유지됨.
사용자 정의 Nest 미들웨어는 함수 또는 @Injectable() 데코레이터가 있는 클래스에서 구현합니다. 함수는 특별한 요구 사항이 없는 반면, 클래스는 NestMiddleware 인터페이스를 구현해야 합니다. 클래스 메소드를 사용하여 간단한 미들웨어 기능을 구현하는 것으로 시작하겠습니다.
WARNING
express
와fastify
는 미들웨어를 다르게 처리하고 다른 메소드 시그니쳐를 제공함. 자세한 내용은 여기로
# 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();
}
}
Nest 미들웨어는 의존성 주입을 완벽하게 지원합니다. provider 및 컨트롤러와 마찬가지로 동일한 모듈 내에서 사용할 수 있는 종속성을 주입할 수 있습니다. 이 작업은 평소와 같이 constructor 통해 수행됩니다.
@Module() 데코레이터에는 미들웨어가 들어갈 자리가 없습니다. 대신 모듈 클래스의 configure() 메소드를 사용하여 설정합니다. 미들웨어를 포함하는 모듈은 NestModule
인터페이스를 구현해야 합니다. AppModule
수준에서 LoggerMiddelware
를 설정해 보겠습니다.
# 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(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats');
}
}
위의 예제에서는 이전에 CatsController
내부에 정의된 /cats route handler에 대한 LoggerMiddleware
를 설정했습니다. 미들웨어를 구성할 때 route path와 요청 메소드가 포함된 객체를 forRoutes()
메소드에 전달하여 미들웨어를 특정 요청 메소드로 더 제한할 수도 있습니다. 아래 예제에서는 원하는 요청 메소드 유형을 참조하기 위해 RequestMethod
열거형을 import한 것을 확인할 수 있습니다.
# 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) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
그러니까 미들웨어를 경로와 HTTP 메소드 별로 제한할 수 있다는 말인 것 같습니다.
HINT
configure
메소드는 async/await을 사용하여 비동기화할 수 있습니다.
WARNING
express
adpater를 사용할 때 NestJS 앱은 기본적으로body-parser
패키지에서json
및urlencoded
를 등록합니다. 즉MiddlewareConsumer
를 통해 해당 미들웨어를 사용자 정의하려면NestFactory.create
로 애플리케이션을 생성할 때bodyParser
플래그를false
로 설정하여 전역 미들웨어를 꺼야 합니다.
패턴 기반 routes 또한 지원합니다. 예를 들어 asterisk(*)는 wildcard로 사용되고 어떤 문자 조합과도 일치합니다.
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
ab*cd
route path는 abcd
, ab_cd
, abecd
등과 일치합니다. 문자 ?
, +
, *
, ()
는 route path에 사용할 수 있으며 정규식 대응 문자의 하위 집합입니다. -
과 .
은 문자열 기반 경로에서 문자 그대로 해석됩니다.
WARNING
fastify
는 더 이상 wildcard*
를 지원하지 않는 최신 버전의
path-to-regexp
패키지를 사용. 대신 매개변수를 사용
(예.(.*)
,:splat*
)
MiddlewareConsumer
는 헬퍼 클래스 입니다. 미들웨어를 관리하기 위한 몇 가지 내장 메소드를 제공합니다. 모든 메소드는 fluent style로 간단히 연결할 수 있습니다.
fluent style을 간단히 설명하면 method chaining에 광범위하게 의존하여 설계된 객체 지향 API입니다. 이 인터페이스의 목표는 도메인 별 언어(Domain-Specific Language, DSL)를 생성하여 코드 가독성을 높이는 것입니다.
forRoutes()
메소드는 단일 문자열, 복수 문자열, RouteInfo
객체, 컨트롤러 클래스, 심지어 여러 컨트롤러 클래스를 받을 수 있습니다. 대부분의 경우 쉼표로 구분된 컨트롤러 리스트만 전달할 것입니다. 아래는 단일 컨트롤러를 사용한 예제입니다.
# app.module.ts
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);
}
}
HINT
apply()
메소드는 단일 미들웨어를 받거나 여러 미들웨어를 지정하기 위해 여러 인수를 받을 수 있음.
때때로 특정 경로를 미들웨어 적용에서 제외하고 싶을 때가 있습니다. exclude()
메소드를 사용하면 특정 경로를 쉽게 제외할 수 있습니다. 이 메소드는 아래와 같이 제외할 경로를 식별하는 단일 문자열, 여러 문자열 또는 RouteInfo
객체를 받을 수 있습니다.
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
HINT
exclude()
메소드는path-to-regexp
패키지를 사용하여 wildcard 매개 변수를 지원함.
우리가 사용한 LoggerMiddleware
클래스는 매우 간단합니다. 멤버, 추가 메소드, 종속성이 없습니다. 클래스 대신 간단한 함수로 정의할 수 없을까요? 가능합니다. 이러한 유형의 미들웨어를 함수형 미들웨어라고 합니다. logger 미들웨어를 클래스 기반에서 함수형 미들웨어로 변환하여 차이점을 설명해 보겠습니다.
# logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
그리고 AppModule
에서 사용하겠습니다.
# app.module.ts
...
consumer
.apply(logger)
.forRoutes(CatsController);
HINT
미들웨어에 종속성이 필요하지 않은 경우 언제든지 더 간단한 기능의 미들웨어를 사용하는 것이 좋음.
위에서 언급했듯이 순차적으로 실행되는 여러 미들웨어를 바인딩하려면 apply()
메소드 안에 쉼표로 구분된 목록을 제공하면 됩니다.
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
등록된 모든 경로에 미들웨어를 한 번에 바인딩하려면 INestApplication
인스턴스에서 제공하는 use()
메소드를 사용할 수 있습니다.
# main.ts
...
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
HINT
전역 미들웨어에서 DI 컨테이너에 접근할 수 없습니다.app.use()
를 사용할 때 함수형 미들웨어를 대신 사용할 수 있습니다. 또는 클래스 미들웨어를 사용하고AppModule
(또는 다른 모듈) 내에서.forRoutes('*')
를 통해 사용할 수 있습니다.
GET localhost:3000
을 호출하면 이렇게
로그가 뜨는 것을 확인하실 수 있습니다!!
CatsController
엔드포인트 호출 시 해당 로그가 두 번 출력되는 이유는 app.module.ts
에서 CatsController
에 미들웨어를 적용했기 때문입니다.
제 코드입니다. 😎
https://github.com/cxzaqq/cxzaqq-velog/tree/2.5-middleware
고생하셨습니다!
다음 글에서 만나요~~😀
저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!
단계마다 깃허브 브랜치가 있어서 좋네요!!