본문 바로가기

Backend/NestJS

[NestJS] MiddleWare(Logger, MiddlewareConsumer)

728x90
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('*');
  }
}

참고

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

 

09장 요청 처리 전에 부가 기능을 수행하기 위한 미들웨어

.

wikidocs.net

 

728x90