NESTJS를 배워보자(6) - Middleware

yoon·2023년 7월 13일
1

NESTJS를 배워보자

목록 보기
6/21
post-thumbnail

Middleware

nest의 공식문서를 토대로 작성합니다.

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

기본적으로 Nest 미들웨어는 express 미들웨어와 동일합니다. 공식 express 문서의 다음 설명은 미들웨어의 기능에 대해 설명합니다.

미들웨어 함수가 수행할 수 있는 작업

  • 코드를 실행
  • 요청 및 응답 객체를 변경
  • 요청-응답 사이클 종료
  • 스택에서 다음 미들웨어 함수를 호출
  • 현재 미들웨어 함수가 요청-응답 사이클을 종료하지 않으면 다음 미들웨어 함수에 제어권을 넘기기 위해 next()를 호출해야 함. 그렇지 않으면 요청이 중단된 상태로 유지됨.

사용자 정의 Nest 미들웨어는 함수 또는 @Injectable() 데코레이터가 있는 클래스에서 구현합니다. 함수는 특별한 요구 사항이 없는 반면, 클래스는 NestMiddleware 인터페이스를 구현해야 합니다. 클래스 메소드를 사용하여 간단한 미들웨어 기능을 구현하는 것으로 시작하겠습니다.

WARNING
expressfastify는 미들웨어를 다르게 처리하고 다른 메소드 시그니쳐를 제공함. 자세한 내용은 여기로

# 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 미들웨어는 의존성 주입을 완벽하게 지원합니다. provider 및 컨트롤러와 마찬가지로 동일한 모듈 내에서 사용할 수 있는 종속성을 주입할 수 있습니다. 이 작업은 평소와 같이 constructor 통해 수행됩니다.

Applying middleware

@Module() 데코레이터에는 미들웨어가 들어갈 자리가 없습니다. 대신 모듈 클래스의 configure() 메소드를 사용하여 설정합니다. 미들웨어를 포함하는 모듈은 NestModule 인터페이스를 구현해야 합니다. AppModule 수준에서 LoggerMiddelware를 설정해 보겠습니다.

# 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 route handler에 대한 LoggerMiddleware를 설정했습니다. 미들웨어를 구성할 때 route path와 요청 메소드가 포함된 객체를 forRoutes() 메소드에 전달하여 미들웨어를 특정 요청 메소드로 더 제한할 수도 있습니다. 아래 예제에서는 원하는 요청 메소드 유형을 참조하기 위해 RequestMethod 열거형을 import한 것을 확인할 수 있습니다.

# 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 });
  }
}

그러니까 미들웨어를 경로와 HTTP 메소드 별로 제한할 수 있다는 말인 것 같습니다.

HINT
configure 메소드는 async/await을 사용하여 비동기화할 수 있습니다.

WARNING
express adpater를 사용할 때 NestJS 앱은 기본적으로 body-parser 패키지에서 jsonurlencoded를 등록합니다. 즉 MiddlewareConsumer를 통해 해당 미들웨어를 사용자 정의하려면 NestFactory.create로 애플리케이션을 생성할 때 bodyParser플래그를 false로 설정하여 전역 미들웨어를 꺼야 합니다.

Route wildcards

패턴 기반 routes 또한 지원합니다. 예를 들어 asterisk(*)는 wildcard로 사용되고 어떤 문자 조합과도 일치합니다.

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

ab*cd route path는 abcd, ab_cd, abecd 등과 일치합니다. 문자 ?, +, *, ()는 route path에 사용할 수 있으며 정규식 대응 문자의 하위 집합입니다. -.은 문자열 기반 경로에서 문자 그대로 해석됩니다.

WARNING
fastify는 더 이상 wildcard *를 지원하지 않는 최신 버전의
path-to-regexp 패키지를 사용. 대신 매개변수를 사용
(예. (.*), :splat*)

Middleware consumer

MiddlewareConsumer는 헬퍼 클래스 입니다. 미들웨어를 관리하기 위한 몇 가지 내장 메소드를 제공합니다. 모든 메소드는 fluent style로 간단히 연결할 수 있습니다.

fluent style을 간단히 설명하면 method chaining에 광범위하게 의존하여 설계된 객체 지향 API입니다. 이 인터페이스의 목표는 도메인 별 언어(Domain-Specific Language, DSL)를 생성하여 코드 가독성을 높이는 것입니다.

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-regexp 패키지를 사용하여 wildcard 매개 변수를 지원함.

Functional middleware

우리가 사용한 LoggerMiddleware 클래스는 매우 간단합니다. 멤버, 추가 메소드, 종속성이 없습니다. 클래스 대신 간단한 함수로 정의할 수 없을까요? 가능합니다. 이러한 유형의 미들웨어를 함수형 미들웨어라고 합니다. logger 미들웨어를 클래스 기반에서 함수형 미들웨어로 변환하여 차이점을 설명해 보겠습니다.

# 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('*')를 통해 사용할 수 있습니다.

GET localhost:3000을 호출하면 이렇게

로그가 뜨는 것을 확인하실 수 있습니다!!

CatsController 엔드포인트 호출 시 해당 로그가 두 번 출력되는 이유는 app.module.ts에서 CatsController에 미들웨어를 적용했기 때문입니다.

제 코드입니다. 😎
https://github.com/cxzaqq/cxzaqq-velog/tree/2.5-middleware

고생하셨습니다!
다음 글에서 만나요~~😀


저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!

profile
백엔드 개발자 지망생

2개의 댓글

comment-user-thumbnail
2024년 6월 12일

단계마다 깃허브 브랜치가 있어서 좋네요!!

1개의 답글