NestJSに最適のORMを考えてみる(Prisma VS TypeORM)

TypeORMの不具合に絶望したあなたのための記事です。

NestJSにおける最適なORMを考えます。
自分の経験とNestJSのDiscordのスレッド「TypeORM or Prisma ?」の内容をもとに書きます。

結論から書くと私個人の意見としてはPrismaKeyselyの併用が良いと思いました。

ORMとクエリビルダを羅列してみる

ORM

  • TypeORM
  • Prisma
  • Sequelize

クエリビルダ

  • Kysely
  • MikroORM
  • DrizzleORM

TypeORM vs Prisma(vs Sequelize)

ORMよいところ微妙なところ
TypeORM・データベースのホットスワップ(例:MySQLからPostgressにスワップするなど)に対応
・複雑なクエリも作れる
・安定性が低い
Prisma・複雑なクエリを作ることができない
・設定ファイル(schema.prisma)が強力
・安定性が低い
・ホットスワップ不可
・複雑なクエリを投げると重い
Sequelizeは使ったことがないので、表に入れていません

私が以前働いていた会社のグループ会社でSequelizeを利用しているチームがあり、運用に耐えられるだけの安定性はあると聞きました。
しかし、書きっぷりは古く、あまり近年は使われていない印象です。

TypeORMを使った感想

トランザクション周りの安定性が貧弱で、ロールバックしたはずなのにデータベースに反映されていたことが過去によくありました🤮

個人的に、コードは読みやすく書きやすく、Prismaより複雑なクエリでも簡単に書けるので、安定性を除けば良いライブラリだと思います。
インターネット上に情報も多いです。

NestJSの公式Discordでは多対多の実装をする時のカーディナリティの設定がダルいという意見がありましたが、その場合は中間テーブルを使って多対多を解消しましょう。

Prismaを使った感想

設定ファイルPrisma.schemaが便利でした。
マイグレーション周りも安定しているし、操作性も気に入ったので、スキーマ管理にはPrismaがおすすめです。

クエリに関してはかなり微妙で、クエリ実行すると初回だけ必ずエラーになったりします。私はこれも絶望してPrismaでクエリを投げるのを諦めました。

また、サブクエリやJOINを書くのも一苦労でした。

ORM vs クエリビルダー vs 生SQL

前見出しで記載した通り、スキーマ管理はPrismaで良いのですが、NodeのORMライブラリはクエリが全然ダメです。

ということでクエリビルダーを使いましょう。

私は、Keyselyしか試していませんが、気に入ったので、これを使います。
複雑なクエリは組めませんが、その場合は、生SQLを書きます。

不安定なORMを使うくらいなら生SQLがよいとおもいます。

KeyselyとPrismaの連携について

この辺りの記事を読むと、Prismaの設定ファイル(prisma.schema)から型ファイル(type.ts)を出力する方法などが書かれています。

PrismaとKyselyの相性もバッチリです👌

Generating types | Kysely

Generating types | Kysely

To work with Kysely, you’re required to provide a database schema type definition to the Kysely constructor.

To work with Kysely, you’re required to provide a database schema type definition to the Kysely constructor.

NestJSにおけるKeyselyのデータベース接続について

NestJSでKeyselyを使ってデータベースを検索する方法などはググっても出てこないかもしれませんが、公式サイトの動的モジュールのところを読めば多分解決できると思います。

Documentation | NestJS – A progressive…

Documentation | NestJS – A progressive…

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, i…

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, i…

本記事の趣旨とズレるため、細かくは解説しませんが以下のようにdatabase.module.tsを作成し、app.module.tsで読み込めばデータベースの接続はできるでしょう。

import { Module, DynamicModule, Global } from '@nestjs/common'
import { db } from 'src/database'

@Global()
@Module({})
export class DatabaseModule {
  static forRoot(): DynamicModule {
    const providers = [
      {
        provide: 'KEYSELY_DB',
        useValue: db,
      },
    ]
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    }
  }
}

app.module.tsはこんな感じでしょう。

@Module({
  imports: [DatabaseModule.forRoot()],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

serviceファイルで以下のように注入して利用します。

import { Inject, Injectable } from '@nestjs/common'
import { InsertObject, InsertResult, Kysely } from 'kysely'
import { DB } from 'src/types'

@Injectable()
export class ExchangesRepository {
  constructor(@Inject('KEYSELY_DB') private db: Kysely<DB>) {}

  async create(hoge: InsertObject<DB, 'hoge'>): Promise<InsertResult> {
    try {
      return await this.db.insertInto('hoge').values(hoge).executeTakeFirst()
    } catch (error) {
      throw new Error('Failed to create hoge.')
    }
  }
}

まとめ

私はPrismaとKeyselyの併用をすることにしました。

スキーマやマイグレーションファイルはPrismaで管理し、クエリの発行はKeyselyで行うイメージです。
複雑なSQLが必要な場合は、迷わず生SQLを書きますが、DBのコネクションはKeyselyを使います。

TypeScriptのORMはまだ安定していて、高機能なものはないようなので、生SQLが最も安定している気がします。

参考

公式Discord:
https://discord.com/invite/nestjs

TypeORM or Prisma ?(Discordのスレッド):
https://discord.com/channels/520622812742811698/1096846405257138226