NestJS 공식문서를 참고한 미들웨어에 대한 공부 기록이다.
개요
미들웨어는 라우트 핸들러보다 먼저 호출되는 함수이다.
Nest의 미들웨어는 기본적으로 Express의 미들웨어와 동일하다.
Express 문서에서 기술된 미들웨어의 역할은 아래와 같다.
- 어떤 형태의 코드라도 수행할 수 있다.
- 요청과 응답에 변형을 가할 수 있다.
- 요청/응답 주기를 끝낼 수 있다.
- 여러 개의 미들웨어를 사용한다면 next()로 호출 스택상 다음 미들웨어에 제어권을 전달한다.
요청/응답 주기를 끝낸다는 것은 응답을 보내거나 에러 처리를 반드시 해야 한다는 의미이다.
만약 현재 미들웨어가 응답 주기를 끝내지 않을 것이라면 반드시 next()를 호출해야 한다.
그렇지 않으면 어플리케이션은 더 이상 아무것도 할 수 없는 상태가 된다.
Nest에서 많이 사용하는 미들웨어의 용도는 아래와 같다.
- 쿠키 파싱 : 쿠키를 파싱하여 사용하기 쉬운 데이터 구조로 변경한다.
- 세션 관리 : 세션 쿠키를 찾고, 해당 쿠키에 대한 세션의 상태를 조회해서 요청에 세션 정보를 추가한다.
- 본문 파싱 : 본문은 POST/PUT 요청으로 들어오는 JSON 타입뿐 아니라 파일 스트림과 같은 데이터도 있다. 이 데이터를 유형에 따라 읽고 해석한 다음 매개변수에 넣는 작업을 한다.
Logger Middleware
미들웨어는 함수로 작성하거나 NestMiddleware 인터페이스를 구현한 클래스로 작성할 수 있다.
들어온 요청에 포함된 정보를 로깅하기 위한 Logger를 미들웨어로 구현해 보자.
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();
}
}
미들웨어를 모듈에 포함시키기 위해서는 해당 모듈이 NestModule 인터페이스를 구현해야 한다.
해당 모듈의 imports 등으로 포함시킬 수 없기 때문이다.
//app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('users');
}
}
위처럼 AppModule로 NestModule을 구현해서 configure 메서드를 이용해 미들웨어를 설정한다.
MiddlewareConsumer
configure 메서드에 인자로 전달된 MiddlewareConsumer 객체를 이용해 미들웨어를 어디에 적용할지 관리할 수 있다.
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('users');
}
MiddlewareConsumer의 정의는 아래와 같다.
export interface MiddlewareConsumer {
/**
* @param {...(Type | Function)} middleware middleware class/function or array of classes/functions
* to be attached to the passed routes.
*
* @returns {MiddlewareConfigProxy}
*/
apply(...middleware: (Type<any> | Function)[]): MiddlewareConfigProxy;
}
apply라는 메서드를 가지는데 이 apply는 함수나 클래스형의 middleware 혹은 그 배열을 받는다.
그리고 MiddlewareConfigProxy를 반환한다. 정의를 보자.
export interface MiddlewareConfigProxy {
/**
* Excludes routes from the currently processed middleware.
*
* @param {(string | RouteInfo)[]} routes
* @returns {MiddlewareConfigProxy}
*/
exclude(...routes: (string | RouteInfo)[]): MiddlewareConfigProxy;
/**
* Attaches passed either routes or controllers to the currently configured middleware.
* If you pass a class, Nest would attach middleware to every path defined within this controller.
*
* @param {(string | Type | RouteInfo)[]} routes
* @returns {MiddlewareConsumer}
*/
forRoutes(...routes: (string | Type<any> | RouteInfo)[]): MiddlewareConsumer;
}
MiddlewareConfigProxy는 미들웨어가 동작할 라우터를 지정하는 역할을 한다.
때문에 라우터를 지정하는 forRoutes와 제외하는 exclude 메서드를 가지고 있다.
forRoutes 메서드를 이용하면 라우터를 지정하는 것뿐만 아니라 method 또한 정할 수 있다.
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
공식문서에 나와있는 예제에서는 cats 경로인 동시에 GET method의 요청을 처리하는 미들웨어를 구현했다.
보통 forRoutes는 위처럼 url을 직접 지정하기보다는 컨트롤러 클래스를 직접 전달한다.
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes(UsersController);
}
}
Functional Middleware
클래스형 미들웨어도 쉽게 구현이 가능하지만, 함수형 미들웨어는 더 간단하다.
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
Nest 공식 문서에 있는 함수형 미들웨어이다.
하지만 함수형 미들웨어는 DI 컨테이너를 사용할 수 없다. 즉 프로바이더를 주입받을 수 없다는 것이다.
때문에 의존성이 없는 간단한 미들웨어일 경우 함수형 미들웨어를 사용하는 것이 좋다.
Global Middleware
미들웨어는 전역적으로도 사용이 가능하다.
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
위처럼 main.ts 파일에서 app에 대해 use 메서드를 이용해 글로벌 미들웨어를 지정해 줄 수 있다.
하지만 글로벌 미들웨어에서는 의존성을 주입받을 수 없다. 그러므로 함수형 미들웨어를 사용하는 것이 좋다.
만약 글로벌 미들웨어로 사용되어야 하지만 의존성을 주입해야 하는 미들웨어라면
아래와 같이 구현할 수도 있다.
//app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('*');
}
}
참고
'Backend > NestJS' 카테고리의 다른 글
[NestJS] 예외 필터(HttpException, Error) (0) | 2023.02.14 |
---|---|
[NestJS] Logger(Custom, Winston) (0) | 2023.02.14 |
[NestJS] NestJS 공식 문서 정독(2)(Dynamic Module) (0) | 2023.01.26 |
[NestJS] NestJS 공식 문서 정독(1)(Custom Provider,Async Provider) (0) | 2023.01.24 |
[NestJS] 커스텀 유효성 검사기 작성(커스텀 데커레이터) (0) | 2023.01.22 |