NestJS에서의 미들웨어

bin-lee·2022년 1월 4일

미들웨어(middleware)

미들웨어는 라우트 핸들러 이전에 호출되는 컴포넌트다. 라우트 핸들러란 엔드포인트마다 동작을 수행하는 컴포넌트를 뜻한다. 라우트 핸들러는 요청 경로와 컨트롤러를 매핑해 주고, 미들웨어는 이런 라우트 핸들러 이전에 실행된다.

미들웨어는 다음과 같은 동작을 할 수 있다.

  • 어떤 코드든 실행 가능하다
  • 요청과 응답에 변형을 가할 수 있다
  • 요청-응답 생명주기를 끝낼 수 있다
  • 미들웨어는 순서가 있다. 여러 개의 미들웨어를 사용 중이라면 next()로 다음 미들웨어에게 제어권을 전달한다.

요청-응답 생명주기를 끝낸다는 것은 (1) 응답을 보내거나 (2) 에러 처리를 하는 것을 뜻한다. 생명주기를 끝내지 않을 때에는 next() 호출로 다음 미들웨어에게 제어권을 주어야 한다.


미들웨어로 수행하는 작업들은 보통 다음과 같다.

  • 쿠키 파싱 : 쿠키를 파싱하여 사용하기 쉬운 데이터 구조로 변경한다.
  • 세션 관리 : 세션 쿠키를 찾고 상태를 조회해서 요청에 세션 정보를 추가한다.
  • 인증/인가 : 사용자가 서비스에 접근 가능한 권한이 있는지 확인한다.
  • body 파싱 : POST/PUT 요청으로 들어오는 json 타입의 본문 및 파일 스트림과 같은 데이터들을 유형에 따라 해석하여 파라미터에 넣는 작업을 한다.

🌼 CLI를 통한 미들웨어 생성과 구현

$ nest g middleware 미들웨어명

일전에 모듈을 생성했던 것과 같은 방식으로 미들웨어를 생성할 수 있다. 생성한 미들웨어의 기본 코드 기반으로 logger를 구현해 봤다.

import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private logger = new Logger();

  use(req: Request, res: Response, next: NextFunction) {
    res.on('finish', () => {
      this.logger.log(
        `${req.ip} ${req.method} ${res.statusCode}`,
        req.originalUrl,
      );
    });
    next();
  }
}

첫 번째 특징으로는 express에서 봤었던 usenext()다. 실제로 Nest의 미들웨어는 기본적으로 express 미들웨어와 동일하다. Nest 역시 express의 미들웨어처럼 use를 이용해서 미들웨어를 사용할 수 있다.

두 번째 특징으로는 서비스단에서 봤었던 @Injectable()가 미들웨어에서도 보인다는 점이다. 즉, 의존성 주입으로 모듈단에서 공급자로 취급되었던 서비스단처럼 미들웨어 역시 의존성 주입을 할 수 있다.

이렇게 생성된 미들웨어는 바로 사용할 수 없다. 공급자는 만들어졌으나, app.module 모듈에 보내 주어야(의존성 주입을 해 주어야) 실행할 수 있다. 다만 다른 공급자들처럼 @module() 데코레이터 내에 넣는 게 아니라 모듈 클래스의configure() 메서드를 통해 미들웨어를 설정한다. 공식 홈페이지에서 제공하는 코드를 적용한 예는 다음과 같다.

import { CatsModule } from './cats/cats.module';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('cats');
  }
}

LoggerMiddleware는 위 코드에서 볼 수 있듯이 의존성 주입이 가능한 제공자(provider)다. 그리고 consumer.apply(LoggerMiddleware) 는 말 그대로 consumer 소비자에게 LoggerMiddleware를 제공해 준다는 뜻이다. NestModule은 약속을 추가해 준 인터페이스를 의미한다.

forRoutes('cats')cats 라우터에 바인딩을 해 주는 것이다. '*' 와일드카드를 넣으면 전체 경로를 뜻하게 된다. 이렇게 .forRoutes 뒤에 오는 경로로 들어오는 요청을 수행하면 logger.middleware의 use 안의 내용이 실행되는 것을 확인할 수 있다.

profile
🚀 오늘 배운 건 오늘 적자

0개의 댓글