심심한 개발자의 취미생활

Middleware

  • Middleware는 라우트 핸들러가 실행되기 전에 호출되는 함수이다.
  • Express의 미들웨어와 기본적으로 동일하며, 요청(Request) 및 응답(Response) 객체와 애플리케이션의 요청-응답 주기에서 그 다음의 미들웨어 함수(next()) 접근할 수 있다.

Middleware 구현

  • NestJS에서 Middleware는 두 가지 방식으로 구현할 수 있다.
    1. 클래스(Class) 기반 미들웨어

      • @Injectable() 데코레이터가 적용된 클래스로 구현하며 이 클래스는 NestMiddleware 인터페이스를 구현해야 한다.
       // logger.middleware.ts
       import { Injectable, NestMiddleware} from '@nestjs/common'
       import { Request, Response, NestFunction } from 'express'
      
       @Injectable()
       export class LoggerMiddleware implements NestMiddleware {
           use(req: Request, res: Response, next: NextFunction) {
               // 수행 내용
               next();
           }
       }
    2. 함수(Functional) 기반 미들웨어

      • 특별한 요구사항 없이 간단한 함수로 구현 가능하며, 의존성 주입이 필요 없는 간단한 미들웨어에 적합하다.
       // logger.middleware.ts
       import { Request, Response, NestFunction } from 'express'
      
       export function logger(req: Request, res: Response, next: NextFunction){
           // 수행 내용
           next();
       }

Middleware 적용

  • Middleware@Module() 데코레이터에서 설정하는 것이 아니라, 모듈 클래스의 configure()메서드를 사용하여 설정한다. 미들웨어를 포함하는 모듈은 NestModule 인터페이스를 구현해야 한다.
  • MiddlewareConsumer는 미들웨어 관리를 돕는 헬퍼 클래스이며, apply()forRoutes() 같은 메서드를 제공한다.
// 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);     // 특정 컨트롤러의 모든 라우트에 적용
    }
}

Middleware 조건부 적용

특정 라우트 적용

  • forRoutes() 메서드에 문자열로 된 경로를 전달하여 특정 라우트에만 미들웨어를 적용할 수 있다. 특정 HTTP 요청 메서드에만 적용할려면 경로와 메서드를 포함한 객체를 전달한다.
consumer
    .apply(LoggerMiddleware)
    .forRoutes({ path: 'cats', method: RequestMethod.GET }) // 'cats' 경로의 GET 요청에만 적용

라우트 와일드 카드

  • 경로에 와일드 카드(*)를 사용하여 패턴 기반으로 라우트를 지정할 수 있다.
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })

특정 라우트 제외

  • exclude() 메서드를 사용하면 특정 라우트를 미들웨어 적용 대상에서 제외할 수 있다.
consumer
    .apply(LoggerMiddleware)
    .exclude(
        { path: 'cats', method: RequestMethod.GET },    // 'cats' 경로의 GET 요청 제외
        { path: 'cats', method: RequestMethod.POST },   // 'cats' 경로의 POST 요청 제외
    )
    .forRoutes(CatsController); // CatsController의 나머지 라우트에 적용

여러 미들웨어 적용

  • apply() 메서드에 여러 미들웨어를 쉼표로 구분하여 전달하면 순차적으로 실행되는 여러 미들웨어를 바인딩 할 수 있다.
consumer
    .apply(cors(), helmet(), logger)
    .forRoutes(CatController);

전역 (Global) 미들웨어

  • 애플리케이션의 모든 라우트에 미들웨어를 한 번에 적용하려면 main.ts 파일에서 INestApplication 인스턴스의 use() 메서드를 사용한다.
// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger) // 'logger' 미들웨어를 전역으로 적용
await app.listen(3000)
  • 이 방식으로는 의존성 주입(DI) 컨테이너에 접근할 수 없어, DI가 필요한 경우, 모듈 내에서 consumer.apply(MyMiddleware).forRoutes('*')와 같이 모든 라우트에 적용하는 방식을 사용해야 한다.

의존성 주입 (Dependency Injection)

  • 클래스 기반 미들웨어는 NestJS의 다른 구성요소(Provider, Controller)와 마찬가지로 의존성 주입을 완벽하게 지원한다. 생성자를 통해 같은 모듈 내에서 사용가능한 의존성을 주입 받을 수 있다.