ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Nest.js 공식문서 읽기] Middleware
    Server/Nest.js 2023. 1. 27. 15:04

    Middleware

    https://docs.nestjs.com/middleware

    미들웨어란 라우트핸들러 전에 호출되는 함수이다.

    미들웨어 함수는 req, res 객체에 접근 가능하며 애플리케이션의 요청-응답 주기에서 next() 미들웨어 함수에 접근할 수 있다.

    Nest 미들웨어는 express의 미들웨어와 같다.

    📌 Official express documentation describes the capabilities of middleware. Middleware functions can perform the following tasks:

    execute any code. make changes to the request and the response objects. end the request-response cycle. call the next middleware function in the stack. if the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

    - 출처 : nest.js 공식문서

     

    Nest에서는 커스텀 미들웨어를 함수 또는 클래스로 구현할 수 있는데, 클래스로 구현할 때는 @Injectable() 데코레이터를 사용해야 하고, 반드시 NestMiddleware 인터페이스를 구현(implement)해야 한다.(함수로 구현할 때는 별다른 조치를 하지 않아도 됨)

    // 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는 미들웨어의 의존성 주입도 지원하기 때문에 providers나 controller와 마찬가지로 동일한 모듈내의 providers나 controller에서 constructor를 사용하여 의존성을 주입해줄 수 있다.

    함수형 미들웨어는 express와 동일하게 만들면 된다.

    // logger.middleware.ts
    
    import { Request, Response, NextFunction } from 'express';
    
    export function logger(req: Request, res: Response, next: NextFunction) {
      console.log(`Request...`);
      next();
    };
    

     

    Applying middleware

    @Module() 데코레이터에는 미들웨어를 등록할 수 없다. 대신에 모듈 클래스의 configure() 메소드를 사용하여 등록할 수 있다. 그리고 미들웨어를 포함한 모듈은 반드시 NestModule 인터페이스를 구현(implement)해야 한다.

    //app.module.ts
    
    import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { BoardsModule } from './boards/boards.module';
    import { typeORMConfig } from './configs/typeorm.config';
    import { LoggerMiddleware } from './common/middleware/logger.middleware';
    
    @Module({
      imports: [TypeOrmModule.forRoot(typeORMConfig), BoardsModule**]**,
    })
    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(**LoggerMiddleware**)
          .forRoutes(**'boards'**);
      }
    }
    

    위 코드 예시에서 LoggerMiddleware를 /boards 라우터 핸들러(BoardsController에서 정의한)에 등록했다.

    이 방법 말고도, 미들웨어를 구성할 때 루트 경로와 요청 메소드를 포함하는 객체를 forRoutes()메서드에 전달하여 미들웨어를 특정 요청방법으로 제한할 수도 있다.

    //app.module.ts
    
    import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { BoardsModule } from './boards/boards.module';
    import { typeORMConfig } from './configs/typeorm.config';
    import { LoggerMiddleware } from './common/middleware/logger.middleware';
    
    @Module({
      imports: [TypeOrmModule.forRoot(typeORMConfig), BoardsModule**]**,
    })
    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(LoggerMiddleware)
          .forRoutes(**{ path : 'boards', method: RequestMethod.GET }**);
      }
    }
    

    또한, configure() 메소드는 async/await 방식으로 비동기로 만들수 있다.

    함수형 미들웨어를 루트모듈에 등록하는 것은 클래스 미들웨어를 등록하는 것과 방식은 동일하다.

     

    Middleware consumer

    MiddlewareConsumer는 helper class이다. 이 클래스는 미들웨어를 제어할 수 있는 몇 개의 빌트인 메소드를 제공하고, 그 메소드들은 간단하게 fluent style로 체이닝할 수 있다.

    • forRoutes() : 이 메소드는 인자로 라우팅 경로(a string, multiple strings)를 받거나 RouteInfo 객체, controller class(하나 또는 여러개)를 받는다. 여러개의 컨트롤러 클래스를 인자로 받을 경우 콤마(,)로 나열한다.
    • apply() : 이 메소드는 미들웨어를 적용하는 메소드이다. 하나의 미들웨어를 받거나 여러개의 인수를 사용하여 여러개의 미들웨어를 지정할 수 있다.
    //app.module.ts
    
    import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { BoardsModule } from './boards/boards.module';
    import { typeORMConfig } from './configs/typeorm.config';
    import { LoggerMiddleware } from './common/middleware/logger.middleware';
    import { BoardsController } from './boards/boards.controller';
    
    @Module({
      imports: [TypeOrmModule.forRoot(typeORMConfig), BoardsModule**]**,
    })
    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer) {
        consumer
          .apply(LoggerMiddleware)
          .forRoutes(BoardsController);
      }
    }
    

     

    Excluding routes

    특정 라우트를 미들웨어 적용에서 제외하고 싶을 때 exclude() 메서드를 사용한다.

    이 메서드는 인자로 라우팅 경로(a string, multiple strings)를 받거나 RouteInfo 객체를 받을 수 있다.

    forRoutes() 메서드처럼 와일드카드 변수를 받을 수 있지만, path-to-regexp 패키지를 사용해야 한다.

    consumer
          .apply(LoggerMiddleware)
    			.exclude(
    				{ path: 'boards', method: RequestMethod.GET },
    				{ path: 'boards', method: RequestMethod.POST },
    				'boards/(.*)',
    			)
          .forRoutes(BoardsController);
    

     

    Multiple middleware

    순차적으로 실행되는 여러개의 미들웨어를 등록하려면 apply() 메소드 내부에 콤마로 구분된 목록을 인자로 보내면 된다.

    consumer.apply(cors(), helmet(), logger).forRoutes(BoardsController);

     

    Global middleware

    미들웨어를 모든 라우트에 한번에 바인딩하려면 INestApplication 인스턴스에서 제공하는 use() 메서드를 사용한다.

    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { logger } from './common/middleware/logger.middleware';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    	app.use(logger)
      await app.listen(3000);
    }
    bootstrap();
    

    댓글