Nest) 요청부터 응답까지(Interceptor와 MiddleWare)

김명성·2023년 10월 22일

인터샙터, 가드, 미들웨어 등 Nest에서 제공해주는 메서드를 사용하다보면, 작성한 코드에 잘못된 부분이 없는데도 작동하지 않는 오류를 마주칠 수 있다.

이번 프로젝트에서 작성한 커스텀가드 AdminGuard가 그런 경우였는데, Nest가 요청을 처리하는 순서에 대해 명확히 알지 못해 발생된 오류였다.

클라이언트로부터 받은 요청을 응답으로 내보내기까지 Nest가 어떤 처리를 하는지 알아보자.

1.Request
2.MiddleWare
3.Guards
4.Interceptor(before Reach to RequestHandler)
5.Request handler
6.Interceptor(After Reach to RequestHandler)
7.Response

위와 같은 순서로 Nest는 처리한다.
AdminGuard의 코드는 다음과 같다


import { CanActivate, ExecutionContext } from "@nestjs/common";




export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest();
   // interceptor를 통해 받을 수 있는 값 currentUser
    if(!request.currentUser) {
      return false
    }
    return request.currentUser.admin;
  }
}

사용자로부터 Request(1)를 받으면 MiddleWare(2)가 실행되고, 그 다음 Guard(3)가 실행되는데, 위 AdminGuardrequest.currentUser의 값은 인터샙터(4)에서 넣어주는 값이다.
즉, currentUser의 값을 처리하는 인터샙터에 도달하기 이전에 Guard에서 currentUser를 받아 항상 undefined(false)를 반환하게 되는 것이었다.

이 부분을 수정하기 위해는 Guard 단계가 실행되기 이전에 currentUser의 값을 받을 수 있는 로직을 인터셉터 단계가 아닌 미들웨어 단계로 작성해야 한다.

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { UsersService } from '../users/users.service';

@Injectable()
export class CurrentUserMiddleWare implements NestMiddleware {
  constructor(
    private usersService: UsersService
  ) {}
  async use(req: Request, res:Response, next: NextFunction) {
    const { userId } = req.session || {}
    if(userId) {
      const user = await this.usersService.findOne(userId)
      req.currentUser = user
    }
    next();
  }
}

이때 currentUser 부분에 에러가 출력되는데, 이유는 Request 인터페이스에는 currentUser가 포함되어 있지 않기 때문이다.

아래와 같이 인터페이스를 커스텀하여 문제를 해결할 수 있다.

interface CustomRequest extends Request {
  currentUser: User;
}
  async use(req: CustomRequest, res:Response, next: NextFunction) {
    // ...
  }
// ...

또는 global.d.ts 파일을 작성하여 Request 인터페이스 속성을 바꿔줄 수 있다.

// types/global.d.ts
import { User } from "./users/user.entity";

declare global {
  namespace Express {
    interface Request {
      currentUser?: User;
    }
  }
}

이제 적용할 모듈에 consumer를 통해 미들웨어를 부착한다.

// users.module.ts
import { Module,MiddlewareConsumer } from '@nestjs/common';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { AuthService } from './auth.service';
import { CurrentUserMiddleWare } from '../middleware/current-user.middleware';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [
    UsersService,
    AuthService,
  ],
});

export class UsersModule {
  configure(consumer:MiddlewareConsumer) {
    consumer.apply(
      CurrentUserMiddleWare // usersModule에서 항상 실행됨
    ).forRoutes('*')
  }
}

0개의 댓글