NestJS で「Do not know how to serialize a BigInt」というエラーが発生した場合の対処法

タイトル通り、NestJS で開発を進める中で生じた「Do not know how to serialize a BigInt」というエラーが生じた際の対処法についてメモ書き。

## 原因

> [Nest] 4865  - 2023/03/19 19:06:06   ERROR [ExceptionsHandler] Do not know how to serialize a BigInt
TypeError: Do not know how to serialize a BigInt

直訳すると、「BigInt をシリアライズする方法を知りません」といった感じでしょうか...。

https://qiita.com/tsutorm/items/08eac95c123c7f991d2e

そもそもの仕様として、JSON においては BigInt のシリアライズに対応していないようです。

## 対処法

これに対するアプローチは色々と存在するようですが、NestJS でどう対処すれば良いのか調べてみました。

状況によっても異なるとは思いますが、今回は下記のようなケースを想定としています。

- ユーザー一覧の情報を JSON 形式で取得できる `/api/v1/users` というエンドポイント
- ユーザーの情報には BigInt 型のデータが含まれる

たとえば、こんな感じのコードがあるとして...

```ts:./src/controllers/users/users_controller.ts
import { Controller, Get } from '@nestjs/common';
import { IsString, IsInt, IsArray } from 'class-validator';

class UserDto {
  @IsInt()
  id: number;

  @IsInt()
  bigIntId: bigint;

  @IsString()
  name: string;

  constructor(partial: Partial<UserDto>) {
    Object.assign(this, partial)
  }
}

export class FindAllUserResponseDto {
  @IsArray()
  users: UserDto[]

  constructor(partial: Partial<FindAllUserResponseDto>) {
    Object.assign(this, partial)
  }
}

@Controller({ path: 'users', version: '1' })
export class UsersController {
  @Get('/users')

  async findAll() {
    const users = [
      { id: 1, bigIntId: BigInt(12345678901234), name: '山田 太郎' },
      { id: 2, bigIntId: BigInt(56789012345678), name: '鈴木 一郎' },
    ];

    return new FindAllUserResponseDto({
      users: users.map((user) => {
        return new UserDto({
          id: user.id,
          bigIntId: user.bigIntId,
          name: user.name,
        })
      })
    })
  }
}
```

このままだと先述のエラーが発生してしまいました。

エラーを解消するためには、以下のように修正します。

```ts:./src/controllers/users/users_controller.ts
import { Controller, Get, UseInterceptors, ClassSerializerInterceptor } from '@nestjs/common';
import { IsString, IsInt, IsArray } from 'class-validator';
import { Transform } from 'class-transformer';

class UserDto {
  @IsInt()
  id: number;

  @IsInt()
  @Transform(({ value }) => value.toString()) // BigIntを文字列に変換する
  bigIntId: bigint;

  @IsString()
  name: string;

  constructor(partial: Partial<UserDto>) {
    Object.assign(this, partial)
  }
}

export class FindAllUserResponseDto {
  @IsArray()
  users: UserDto[]

  constructor(partial: Partial<FindAllUserResponseDto>) {
    Object.assign(this, partial)
  }
}

@Controller({ path: 'sites', version: '1' })
export class SitesController {
  @Get('/sites')
  @UseInterceptors(ClassSerializerInterceptor) // ClassSerializerInterceptor を使用する

  async findAll() {
    const users = [
      { id: 1, bigIntId: BigInt(12345678901234), name: '山田 太郎' },
      { id: 2, bigIntId: BigInt(56789012345678), name: '鈴木 一郎' },
    ];

    return new FindAllUserResponseDto({
      users: users.map((user) => {
        return new UserDto({
          id: user.id,
          bigIntId: user.bigIntId,
          name: user.name,
        })
      })
    })
  }
}
```

- UserDto で @Transform デコレーターを使用して、BigInt の値を文字列に変換
- ClassSerializerInterceptor を使用して、UserDto をシリアル化

このように、NestJS では ClassSerializerInterceptor と @Transform デコレーターの組み合わせで BigInt をシリアライズすることができました。

```json
[
	{
		"id": "1",
		"bigIntId": "12345678901234",
		"name": "山田 太郎"
	},
	{
		"id": 2,
		"bigIntId": "56789012345678",
		"name": "鈴木 一郎"
	}
]
```