-
Notifications
You must be signed in to change notification settings - Fork 0
Nest.js Overview Middleware
미들웨어는 라우트 핸들러 전에 호출되는 하나의 함수입니다. 미들웨어 함수는 request 및 response 객체에 접근할 수 있으며 애플리케이션의 request-response 사이클 안에 있는 next() 미들웨어 함수에 액세스할 수 있습니다. next 미들웨어 함수는 보통 next라는 이름의 변수로 표시됩니다.
Nest 미들웨어는 기본적으로 express 미들웨어와 같습니다(!다만 우리가 fastify를 플랫폼으로 채택했다면 조금 다를 수 있습니다). 다음은 express 문서에서 발췌한 미들웨어의 기능입니다.
미들웨어 함수는 다음과 같은 작업을 수행할 수 있습니다.
- 코드의 실행
- request, response 오브젝트의 변형
- request-response cycle을 끝내기
- stack에 있는 다음 미들웨어 함수 실행
- 현재 미들웨어가 req-res 사이클을 끝내지 않으면 반드시
next()
를 호출하여 컨트롤을 다음 미들웨어 함수에 주어야 합니다. 그렇지 않으면 request는 hanging 상태로 머물게 됩니다.(응답을 주지 않은 채 대기)
우리는 Nest 미들웨어를 함수 또는 @injectable()
데코레이터를 포함한 클래스로 만들 수 있습니다. 클래스는 NestMiddleware
인터페이스를 implements
해야합니다. 함수는 특별한 준비물이 필요 없습니다. 그럼 클래스 메소드로 심플한 미들웨어 기능을 만들어봅시다.
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()
}
}
네스트 미들웨어는 의존성 주입을 fully support 합니다. 프로바이더와 컨트롤러처럼 동일 모듈 내에서 의존성을 주입할 수 있습니다. 평소처럼 constructor를 통해 주입됩니다.
@Module()
데코레이터에는 미들웨어를 위한 자리가 없습니다. 대신 Module 클래스의 configure()
메소드를 통해 장착합니다. 미들웨어를 가지고 있는 모듈은 NestModule
인터페이스를 implements 해야합니다. 다음은 LoggerMiddleware
를 AppModule
레벨에 장착한 예시입니다.
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')
}
}
위의 예시에서는 /cats
라우트에 대해 LoggerMiddleware
를 적용했습니다. forRoutes()
메소드에 path, method등을 포함한 객체를 전달하여 좀 더 스페시픽한 request에만 지정할수도 있습니다. 아래 예시가 그렇습니다. (RequestMethod
enum을 이용했습니다.)
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()
메소드는 비동기로 만들 수도 있습니다.(async/await)
패턴화된 라우트도 지ㅜ언됩니다. 예를들어 다음은 *
(asterisk) 가 와일드카드로 쓰였습니다.
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })
ab*cd
라우트는 abcd
, ab_cd
등에 매칭됩니다. ?
, +
, *
및 ()
도 경로에 사용될 수 있습니다. -
와 .
은 그대로 해석됩니다.
fastify에서는 오래된 버전의
path-to-regex
를 쓰기 때문에*
와일드 카드를 지원하지 않습니다. 대신 반드시 parameter를 써야합니다. (e.g.(.*)
,:splat*
)
MiddlewareConsumer
는 helper class 입니다. 미들웨어를 관리하기 위한 몇가지 내장 메서드를 제공합니다. 모두 fluent style(자바스크립트의 체이닝 메소드와 같은 디자인 패턴입니다. 모든 메서드의 리턴값이 자기 자신-this, 메서드가 속한 클래스-을 리턴하는 식으로 구현합니다)로 chained 될 수 있습니다. forRoutes()
메서드는 하나 또는 여러개의 스트링, RouteInfo
객체, 하나 또는 여러개의 컨트롤러 클래스를 받을 수 있습니다. 대부분의 경우 우리는 그냥 컴마로 구분된 컨트롤러들을 전달하면 됩니다. 아래의 예시에서는 하나의 컨트롤러만 전달합니다.
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()
메소드 또한 하나 이상의 미들웨어를 전달 받을 수 있습니다. 👉 자세히 multiple middelwares
때때로 미들웨어가 적용될 때 어떤 routes는 제외하고 싶을 수 있는데요. exclude()
메소드를 사용하면 됩니다. 이 메서드는 forRoute()
와 같은 형태의 인자를 받을 수 있습니다.
consumer
.apply(LoggerMiddleware)
.exclude({ path: 'cats', method: RequestMethod.GET }, { path: 'cats', method: RequestMethod.POST }, 'cats/(.*)')
.forRoutes(CatsController)
exclude() 역시 path-to-regex 패키지를 이용해 wildcard 파라미터를 지원합니다.
위의 예시에서는 정의된 3가지 route만 제외하고 미들웨어가 적용됩니다.
예시로 들은 LoggerMiddleware
은 상당히 단순했습니다. 멤버도 없고 메서드도 없고, 의존성도 없었죠. 이럴 땐 단순한 함수로 정의할 수도 있습니다. 이러한 미들웨어 타입을 functional middleware라고 합니다. 이 로거 미들웨어를 펑셔널 미들웨어로 바꿔봅시다.
import { Request, Response, NextFunction } from 'express'
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`)
next()
}
AppModule
에서의 사용법은 아래와 같습니다.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'
import { logger } 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(logger).forRoutes(CatsController)
}
}
functional middleware는 의존성이 필요 없을 때 쓰기 좋습니다.
위에 언급했던 것처럼 여러개의 미들웨어를 순차적(sequentially)으로 실행하기 위해서는 단순히 ,
로 구분하여 전달하면 됩니다.
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController)
모든 라우트에 적용될 미들웨어가 필요하다면 INestApplication
인스턴스의 use()
메서드를 사용할 수 있습니다.(express처럼요)
const app = await NestFactory.create(AppModule)
app.use(logger)
await app.listen(3000)
글로벌 미들웨어에서 DI(의존성 주입) 컨테이너를 접근하는 것은 불가능합니다. 이럴 땐 위의 AppModule의 컨슈머를 사용하는 형식으로 쓰면됩니다.