高瀬博道の技術ブログ

高瀬博道の技術ブログです。

もう2023年になったので、今さらNestJSに入門しようと思う - その16

前回

takasehiromichiex.com

インターセプタ

インターセプタは、@Injectable()デコレータがつけられたクラスで、NestInterceptorクラスを実装します。

インターセプタは、以下が可能です。

  • メソッド実行の前後に追加のロジックを実行する
  • メソッドから返却された結果を変換する
  • メソッドからスローされた例外を変換する
  • 基本的な機能の動作を拡張する
  • 特定の条件に応じて関数をオーバーライドする

各インターセプタは、intercept()メソッドを実装します。

intercept()メソッドは、ExecutionContextインスタンスと、CallHandlerインスタンスを引数に取ります。

CallHandlerは、インターセプタのある時点でルートハンドラメソッドを呼び出すために使用できるhandle()メソッドを持ちます。

intercept()メソッドの実装でhandle()メソッドを呼び出さない場合、ルートハンドラメソッドは実行されません。

インターセプタの実装

それでは、インターセプタを実装してみましょう。

$ nest g interceptor interceptor/logging

以下のファイルが作成されました。

src/interceptor/logging/logging.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle();
  }
}
src/interceptor/logging/interceptor.logging.spec.ts
import { LoggingInterceptor } from './logging.interceptor';

describe('LoggingInterceptor', () => {
  it('should be defined', () => {
    expect(new LoggingInterceptor()).toBeDefined();
  });
});

インターセプタを修正してみましょう。

src/interceptor/logging/logging.interceptor.ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from "@nestjs/common";
import { Observable, tap } from "rxjs";

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log("Before...");

    const now = Date.now();

    return next.handle().pipe(
      tap(() => {
        console.log(`after... ${Date.now() - now}ms`);
      })
    );
  }
}

handle()は、RxJS Observableを返却するので、様々な選択肢があります。

ストリームの終了時、または例外終了時にログを出力します。

コントローラにインターセプタをバインドします。

src/cats/cats.controller.ts
import { Body, Controller, Get, Post, UseInterceptors } from "@nestjs/common";
import { CreateCatDto } from "src/dto/cat/cat.dto";
import { Cat } from "./cats.interface";
import { CatsService } from "./cats.service";
import { ValidationPipe } from "../pipe/validation/validation.pipe";
import { LoggingInterceptor } from "src/interceptor/logging/logging.interceptor";

@Controller("cats")
@UseInterceptors(LoggingInterceptor)
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  create(@Body(new ValidationPipe()) createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
  @Get()
  findAll(): Cat[] {
    return this.catsService.findAll();
  }
}

@UseInterceptors(LoggingInterceptor)を設置して、バインドしました。

まとめ

ここまでで、

  • インターセプタについて
  • インターセプタの実装
  • インターセプタのコントローラへのバインド

について学びました。

次回は、もう少しインターセプタについて掘り下げようと思います。

コード

今回のコードは、以下に格納しました。

github.com

takasehiromichiex.com