Middleware에 대해 알아보자!

HanSH·2024년 1월 17일

NestJS

목록 보기
6/29
post-thumbnail

Middleware란?

라우트 핸들러, 즉 컨트롤러 호출 이전에 실행되는 일종의 "방화벽"같은 개념이다.
RequestResponse 객체에 접근 가능하고, next() 미들웨어 함수로 다음 미들웨어를 호출한다(?).
검색해도 next()와 관련된거는 안나오고 NextJS로만 나오더라...

미들웨어 함수는 다음 동작을 실행할 수 있는데,

  • 어느 코드나 실행할 수 있다.
  • Request와 Response 객체(데이터)를 수정할 수 있다. → 인증, 인가를 middleware단에서 할 수 있다.
  • request-response 사이클을 끝낸다.
  • 미들웨어 스택에 있는 다음 미들웨어를 호출한다.
  • 만약 미들웨어 함수가 request-response cycle의 끝이 아니라면 다음 미들웨어 함수로 제어권을 넘기려면 무조건 next() 함수를 사용해야한다. 그렇지 않으면 요청이 보류된다. (정확히는 미들웨어 체인을 마무리 짓는다.)

사용법

  • 함수로 작성
  • 클래스로 작성
    1. @Injectable() 데코레이터 사용
    2. NestMiddleware 인터페이스 구현(상속받아 함수 재정의)

공식 예제에서는 미들웨어를 src/common/middleware/ 경로에 저장하였다.

작성 - 함수형, 클래스를 사용하지 말자

import { NextFunction } from 'express';

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

작성 - 데코레이터 + 인터페이스, 클래스를 사용하자

// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

// Injectable 데코레이터 사용과 NestMiddleware 구현
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

사용 - apply에 때려박자!

이후 app.module.ts에 다음과 같이 적어주자.

import { MiddlewareConsumer, Module, NestMiddleware, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { LoggerMiddleware } from './common/middleware/logger.middleware';

@Module({
  imports: [CatsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule { // NestModule을 구현하겠다!
    configure( consumer: MiddlewareConsumer) { // consumer를 MiddlewareConsumer로 설정
      consumer
        .apply(LoggerMiddleware) 	// 이 middleware를 사용할건데,
        .forRoutes('cats*')			// 이 경로에 대해서만 해줘(와일드카드 사용 가능)
		// .forRoutes({ path: string, method: RequestMethod}) // path 경로로 이 method로 요청을 할때만 실행해줘
    }
}

여기서 일일히 forRoutes에 경로를 지정할 필요 없이 Controller 자체를 넣어도 된다. 즉,

    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);

로도 가능하다는 얘기!

만약 여기서 CatsController에 있는 경로 이외의 것들도 포함하고 싶다면

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)', // cats/*의 모든 메소드를 받겠다
  )
  .forRoutes(CatsController);

로도 가능하다.
인증, 인가 미들웨어를 만들어놨는데 특정 컨트롤러의 엔드포인드만 필요한 경우 exclude를 이용하여 컨트롤러 전체를 라우팅할 필요가 없어진다.


전역 미들웨어로 사용하고싶다면 main.ts에 app.use로 미들웨어를 추가해주자.
이때는 클래스로 작성하면 안되고 함수형으로 작성해야한다. 아니면 app.module.ts에서 .forRoutes('*')으로 쓰든지.

// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

미들웨어의 중첩

미들웨어는 중첩시 어떻게 동작할까? 테스트를 해보자.

export function test1Middleware(req: Request, res: Response, next: NextFunction) {
  console.log('testing middleware... 1');
  next();
  console.log('testing middleware... 1 end');
}
export class Test2Middleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    console.log('testing middleware... 2');
    next();
    console.log('testing middleware... 2 end');
  }
}
export class AppModule implements NestModule {
    configure( consumer: MiddlewareConsumer) {
      consumer
        .apply(test1Middleware, Test2Middleware) // 여러개의 미들웨어를 한 번에 가능하게 하는 마법
        .forRoutes('cats')
    }
}

// 아래의 AppModule도 같은 결과를 출력한다.
// 어차피 chain이 걸리기 때문
// export class AppModule implements NestModule {
//     configure( consumer: MiddlewareConsumer) {
//       consumer
//         .apply(test1Middleware)
//         .forRoutes('cats')
//         .apply(Test2Middleware)
//         .forRoutes('cats')
//    }
// }

결과는 아래와 같다.

스택 구조로 쌓이는 것이다!

만약 Test1Middleware에서 next()를 없앤다면?

스택이 쌓이지 않는다.
정확히는 스택이 있긴 한데 다음으로 넘기지 않는 것.

참고

  • NestMiddleware 인터페이스는 아래와 같이 되어있다.
export interface NestMiddleware<TRequest = any, TResponse = any> {
	use(req: TRequest, res: TResponse, next: (error?: Error | any) => void): any;
}
  • NestModule 인터페이스는 아래와 같이 정의되어있다.
export interface NestModule {
    configure(consumer: MiddlewareConsumer): any;
  	// 비동기 작업이 가능하다!
  	// async/await가 가능하다는 말
}
profile
저는 말하는 싹 난 감자입니다

0개의 댓글