상똥이의 Back-End 공부방

[Nest.js] Middleware 본문

Nest.JS/Docs

[Nest.js] Middleware

상똥백 2024. 9. 5. 18:23

Middleware

미들웨어는 라우트 핸들러 전에 호출되는 함수입니다. 미들웨어 함수는 요청 및 응답 객체와 애플리케이션의 요청-응답 사이클에서 next() 미들웨어 함수에 접근할 수 있습니다. next 미들웨어 함수는 일반적으로 next라는 변수로 표시됩니다.

네스트 미들웨어는 기본적으로 express 미들웨어와 동일합니다. 아래의 express의 공식문서에서 가져온 설명은 미들웨어의 능력을 서술합니다.:

미들웨어 함수는 아래의 업무를 수행할 수 있습니다.:
    · 어느 코드든 실행할 수 있음
    · 요청과 응답 객체를 변형할 수 있음
    · 요청-응답 사이클을 종료시킴
    · 다음 미들웨어 함수를 호출함
    · 만약 현재 미들웨어가 요청-응답 사이클을 종료하지 않으면, 반드시 next()를 호출해서 통제 권한을 다음 미들웨어로 넘김. 그렇지 않으면 요청이 처리되지 않은 상태로 남게 됨

 

함수나 클래스에서 @Injectable() 데코레이터를 사용해 커스텀 미들웨어를 구현할 수 있습니다. 특별한 요구사항이 없는 이상 클래스는 NestMiddleware 인터페이스를 확장해야합니다. 클래스 메서드를 사용해 간단한 미들웨어를 구현해보겠습니다.

WARNING
Express와 fastify는 미들웨어를 다른 방식으로 처리하고 서로 다른 메서드를 제공합니다. 자세한 내용은 여기에서 확인할 수 있습니다.
// 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();
  }
}

 


Dependency injection

Nest 미들웨어는 의존성 주입을 완벽하게 지원합니다. 프로바이더와 컨트롤러만으로 동일한 모듈에서 사용 가능한 의존성 주입이 가능합니다. 보통은 constructor을 통해 이루어집니다.

 


Applying middleware

@Module() 데코레이터에는 미들웨어를 넣는 곳이 없습니다. 대신, 모듈 클래스의 configure() 메서드를 사용해 설정합니다. 미들웨어를 포함하는 모듈은 NestModule 인터페이스를 확장해야합니다. AppModule레벨에서 LoggerMiddleware를 성정해보겠습니다.

// app.module.ts

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

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

 

위의 예시에서 CatsController 내부에 정의된 /cats 경로 처리기를 위해LoggerMiddleware를 설정했습니다. 미들웨어를 설정할 때 경로와 요청 메서드를 포함한 객체를 forRoutes()로 거침으로써 특정 요청에 대해 제한할 수도 있습니다. 아래의 예시에서 필요한 메서드 타입을 참조하기 위해 RequestMethod의 enum을 사용한 것에 유의하세요.

// app.module.ts

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}
HINT
configure() 메서드는 async/await를 사용해서 비동기로 만들어질 수 있습니다. 
WARNING
express 어댑터를 사용할 경우, Nest 앱은 기본적으로 body-parser 패키지의 json과 urlencoded를 설치할것입니다. 이는 MiddlewareConsumer를 통해 해당 미들웨어를 사용자 지정하려면, NestFactory.create()로 애플리케이션을 생성할 때 bodyParser 플래그를 false 로 설정하여 글로벌 미들웨어를 비활성화해야 한다는 것을 의미합니다.

 


Route wildcards

경로 기반의 패턴을 지원하고 있습니다. 예를 들어 별표(*)는 와일드카드로 사용되며 어느 문자 결합과도 연결됩니다.:

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });

 

'ab*cd' 경로는 abcd, ab_cd, abecd 등을 매칭합니다. 경로 경로에서 ?, +, *, () 문자를 사용할 수 있으며, 이는 정규 표현식의 일부입니다. 하이픈(-)과 점(.)은 문자열 기반 경로에서 그대로 해석됩니다.

WARNING
fastify 패키지는 별표 와일드카드를 더이상 지원하지 않는 path-to-regexp의 가장 마지막 버전을 사용합니다. 대신 .*과 같은 파라미터를 사용해야 합니다.

 


Middleware consumer

MiddlewareConsumer는 헬퍼클래스입니다. 미들웨어를 관리하기 위한 내장 메서드를 제공합니다. fluent 형식으로 간단히 연결지을 수 있습니다. forRoutes() 메서드는 한 개 이상의 문자열, routeInfo 객체, 한 개 이상의 컨트롤러 클래스를 받을 수 있습니다. 대부분의 경우 쉼표로 분리된 컨트롤러 리스트를 통과하기만 할 것입니다. 아래 예시는 단일 컨트롤러를 보여줍니다.:

// 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);
  }
}
HINT
apply() 메서드는 하나의 미들웨어를 받거나, 여러 개의 미들웨어를 지정하기 위해 여러 인수를 받을 수 있습니다.

 


Excluding routes

때로는 특정 경로에 미들웨어가 적용되지 않도록 제외하고 싶을 때가 있습니다. exclude() 메서드를 사용해 쉽게 제외시킬 수 있습니다. 이 메서드는 한 개 이상의 문자 또는 제외시킬 경로를 정의하는 RouteInfo 객체를 받을 수 있습니다.

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)',
  )
  .forRoutes(CatsController);
HINT
exclude() 메서드는 path-to-recap 패키지를 사용하는 와일드카드 파라미터를 지원합니다.

 

위 예시에서 LoggerMiddleware는 CatsController 내부에 정의된 모든 경로에 바인딩되지만, exclude() 메서드에 전달된 세 가지 경로는 제외됩니다.

 


Functional middleware

지금까지 사용해온 LoggerMiddleware는 다소 간단합니다. 멤버도 없고 추가 메서드나 의존성도 없습니다. 그렇다면 왜 이를 단순한 함수로 정의하지 않고 클래스로 정의할까요? 사실 단순한 함수로 정의할 수 있습니다. 이러한 유형의 미들웨어를 함수형 미들웨어라고 합니다. 이 차이를 설명하기 위해 LoggerMiddleware를 클래스 기반에서 함수형 미들웨어로 변환해보겠습니다.:

// logger.middleware.ts

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

 

이를 AppModule에서 사용해보겠습니다.:

// app.module.ts

consumer
  .apply(logger)
  .forRoutes(CatsController);
HINT
의존성이 필요하지 않다면 더 간단한 방식인 함수형 미들웨어를 사용하는 것을 추천합니다.

 


Multiple middleware

위에서 언급했듯이 순차적으로 제외된 여러 개의 미들웨어를 바인딩하기 위해서는 apply() 메서드 안에 쉼표로 구분된 리스트를 넣어주면 됩니다.

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

 


Global middleware

미들웨어를 모든 경로에 단 한번에 바인딩하고 싶다면, INestApplication 인스턴스에서 제공하는 use() 메서드를 사용하면 됩니다.

// main.ts

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
HINT
전역 미들웨어에서는 DI 컨테이너에 접근할 수 없습니다. app.use() 대신 함수형 미들웨어를 사용할 수 있습니다. 또는 클래스 기반 미들웨어를 사용하고, AppModule(또는 다른 모듈) 내에서 .forRoutes('*')로 사용할 수 있습니다.

 

원문: https://docs.nestjs.com/middleware

'Nest.JS > Docs' 카테고리의 다른 글

[Nest.js] Exception filters  (1) 2024.09.08
[Nest.js] Modules  (1) 2024.09.04
[Nest.js] Providers  (0) 2024.09.04
[Nest.js] Controllers  (0) 2024.09.04
[Nest.js] First steps  (0) 2024.03.19