NestJS 빠르게 배우기 04

NestJS 빠르게 배우기 04

지난 글에서는 Module에 대해서 알아봤다. 이번 글에서는 Middleware에 대해서 알아보자.

Middleware

미들웨어는 컨트롤러 바로 전에서 동작하는 함수이다. 미들웨어 함수들은 애플리케이션의 request-response 사이클 중에 requestresponse 오브젝트에 접근할 수 있고, next() 미들웨어 함수에 접근할 수 있다.


공식 문서에서 제공해주는 이미지

Nest의 미들웨어는 기본적으로 express의 미들웨어와 동일하다. express의 공식 문서에서는 미들웨어의 기능을 아래와 같이 기술하고 있다.

Middleware 함수는 아래 기능들을 수행할 수 있다.

  • 어떠한 코드도 실행할 수 있음
  • request-response 사이클을 종료할 수 있다.
  • 다음 미들웨어 함수를 가져올 수 있다.
  • 현재 미들웨어 함수가 request-response 사이클을 종료하는 기능을 하지 않는다면, 그 미들웨어는 반드시 next()함수를 다음 미들웨어에게 통제권을 넘기기 위해서 사용해야 한다. 그렇지 않으면 request는 계속 응답을 기다리는 상태가 된다.

커스텀 Nest Middleware는 함수나 Injectable() 데코레이터와 함께 사용한 클래스로 사용할 수 있다. 클래스의 경우, NestMiddleware 인터페이스를 implement 해야 한다. 반면 함수는 어떤 특별한 요구사항은 없다.

1
2
3
4
5
6
7
8
9
10
11
// logger.middleware.ts
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response } from "express";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Request, next: Function) {
console.log("Request...");
next();
}
}

의존성 주입

Nest 미들웨어는 온전하게 의존성 주입을 지원하고 있다. Provider나 Controller처럼 같은 모듈에서 사용할 수 있는 의존성을 주입하는 것이 가능하다. 그 전과 마찬가지로 constructor에 명시해주는 것으로서 사용할 수 있다. (주: 이 부분은 그 전 글을 읽고 오면 좋을 것 같다.)

미들웨어 적용하기

@Module 데코레이터에는 미들웨어를 넣을 부분이 없다. 대신 미들웨어를 Module 클래스의 configure 메서드를 만들어서 세팅할 수 있다. 미들웨어를 사용하는 모듈들은 NestModule 인터페이스를 implements 해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
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");
}
}

위 예시에서는 LoggerMiddleware/cats 라우터 핸들러에 달았다. 더 디테일한 메서드에서 사용하려면 forRoutes에서 메서드를 설정해줄 수 있다. 아래에 RequestMethod는 요구되는 요청 메서드 타입을 참조하고 있는 enum이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 });
}
}

configure() 메서드는 비동기적으로도 만들어질 수 있다.

와일드 카드 라우팅

라우터에서 적용되던 패턴 베이스의 라우팅이 미들웨어에서도 마찬가지로 적용된다. *를 와일드 카드로 사용할 수 있다.

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

미들웨어 Consumer

MiddlewareConsumer는 헬퍼클래스이다. 이 클래스는 몇 가지 내장된 미들웨어를 관리해주는 메서드를 가지고 있다. 이 모든게, fluent style에서 체인 형태로 적용할 수 있다. forRoutes() 메서드는 하나의 문자열, 다수의 문자열, RouteInfo 객체, 컨트롤러 클래스, 심지어 다수의 컨트롤러 클래스들을 받을 수 있다. 대부분의 경우 몇 개의 컨트롤러들의 리스트를 넘기게 될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

apply() 메서드는 아마 하나의 미들웨어나 여러 미들웨어를 특정하기 위한 다수의 인자값을 받을 수도 있다.

라우터 제외하기

특정 라우트에는 미들웨어 적용을 하고 싶지 않을 수도 있다. 이러한 경우에는 exclude 메서드를 사용해서 쉽게 제외할 수 있다. 이 메서드는 하나의 스트링, 다수의 스트링, 또는 제외될 라우팅을 결정해주는 RouteInfo 객체를 받는다.

1
2
3
4
5
6
7
8
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: "cats", method: RequestMethod.GET },
{ path: "cats", method: RequestMethod.POST },
"cats/(.*)"
)
.forRoutes(CatsController);

위 예시들로 exclude() 메서드에서 정의된 세 가지 예외사항을 제외한 LoggerMiddlewareCatsController 의 라우터들에게 적용된다.

함수형 미들웨어

위 예시에 나온 LoggerMiddleware 클래스는 아주 간단했다. 멤버도 없고 추가 메서드도 없고 의존성도 없다. 이러한 경우는 그냥 함수형 미들웨어를 정의할 수도 있는데, 아래와 같이 작성하면 된다.

1
2
3
4
5
6
// logger.middleware.ts

export function logger(req, res, next) {
console.log(`Request...`);
next();
}
1
2
consumer.apply(logger);
forRoutes(CatsController);

별다른 의존성이 필요 없다면 언제든 함수형 미들웨어를 적용할 것을 고민해보자.

다수의 미들웨어 적용

위에서 언급한 것처럼, 여러 미들웨어를 연속적으로 동작하도록 묶어주기 위해서 쉼표(,)로 분리된 리스트가 apply() 메서드 안에 필요하다.

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

글로벌 미들웨어

미들웨어를 등록된 모든 라우터에 한 번에 적용하려면, INestApplication인스턴스에 의해 제공되는 use() 메서드를 사용해라.

1
2
3
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

후기

미들웨어는 익숙한 방식으로도 작성할 수 있고, 컨트롤러나 특정 라우터마다 적용할 수도 있다고 해서 좋긴 한데, 의존성을 주입하는 예시 부분이 없어서 조금 아쉽다. 다음 장이 예외 처리 인데, 이 부분 까지만 하면 대충은 필수적인 부분은 다 확인한 것 같고, Nest의 특징적인 부분들은 프로젝트를 해가면서 배우는 것도 좋을 것 같다.

Reference

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×