nestjs

Interceptor (인터셉터)

Interceptor (인터셉터)

요청을 변경할수도 있고, 응답을 변경할수도 있음.

기본 구조

@Injectable()
export class LogInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

  }
}

구현

핸들이 실행되었을 때 response가 반환된다. 즉, 응답이 들어온다.

이때 반환된 것이 pipe의 tap내부에 있는 observable로 들어온다.

그렇기 때문에 observable을 통해서 response 값을 볼 수 있다.

  • tap : 모니터링 할 수 있는 함수
  • map : 변형 할 수 있는 함수
@Injectable()
export class LogInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // 요청이 들어올 때 REQ 요청이 들어온 타임스탬프를 찍는다.
    // [REQ] {요청 path} {요청 시간}
    const req = context.switchToHttp().getRequest();
    const path = req.originalUrl;
    const now = Date.now();
    console.log(`[REQ] ${path} ${now.toLocaleString('kr')}`);
    // 여기부터는 라우트의 로직이 전부 실행되고 응답이 observable로 반환된다.
    // 즉, 윗 부분은 로직이 실행되기 전에 요청 부분에서 실행하는 부분.
    // 리턴 부분은 응답 값을 실행하는 부분.
    return next.handle().pipe(
      tap((observable) => console.log(observable)),
      map((observable) => {
        return { message: '응답이 변경되었습니다', response: observable };
      }),
    );
    // 요청이 끝났을 때 (응답이 나갈 때) 다시 타임스탬프를 찍는다.
    // [RES] {요청 path} {응답 시간} {얼마나 걸렸는지 ms}
  }
}

활용

트랜잭션의 경우에 조건에 따라 커밋하거나 롤백하는데 이를 구현하기 위해 try-catch 문을 사용해야함.

하지만 인터셉터를 활용해서도 이 기능을 구현할 수 있음.

@Injectable()
export class TransactionInterceptor implements NestInterceptor {
  constructor(private readonly dataSource: DataSource) {}
  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise<Observable<any>> {
    const req = context.switchToHttp().getRequest();

    // 1. 쿼리 러너 생성 (트랜잭션과 관련된 모든 쿼리 담당)
    const qr = this.dataSource.createQueryRunner();
    // 2. 쿼리 러너 연결
    await qr.connect();
    // 3. 쿼리 러너에서 트랜잭션 시작.
    // 이 시점부터 같은 쿼리러너를 사용하면
    // 트랜잭션 안에서 데이터베이스 액션 실행 가능
    await qr.startTransaction();

    req.queryRunner = qr;

    return next.handle().pipe(
      catchError(async (e) => {
        // 에러가 발생하면 롤백 수행
        await qr.rollbackTransaction();
        await qr.release();
        throw new InternalServerErrorException(e.message);
      }),
      tap(async () => {
        await qr.commitTransaction();
        await qr.release();
      }),
    );
  }
}