TypeScriptでバックエンドを書いちゃう皆様、ORMは何をお使いですか?
私はシェアが大きい Prismaを使ってみたんですが、どうも肌に合いません。その理由は以下のようなものです。
2025.01.07 追記
まったく同じ理由で自分と同じ構成にしている方がいましたので、こちらどうぞ! Prisimaやめたポイントが同じ!
1. Prisimaの独自スキーマ、いらない
ORMの特徴として、テーブルのスキーマに合わせてカラムの型、ENUM、外部キー、ユニーク制約などを生成してくれます。Prismaはこれを独自のDSLで実現し、npx prisma generateでスキーマを生成します。
自分だけかもしれませんが、ORMのスキーマに細かいリレーションシップの定義は不要です。ActiveRecordの hasMany的な定義を事前に入れるのはだるいし、JOIN条件はクエリで組み立てればいいじゃん派。JOIN条件が複合条件になる場合もあるし。enumは欲しいけどね。
そんなに難しくないDSLですけど、DSLを元にコードを自動生成する設計の場合、結局DSLの中身を理解して、クエリを組み立てないといけない。PrismaのWhere句に相当するクエリの書き方は、個人的に肌に合わなかった。覚えるのがだるい。
Prismaのようなスキーマ生成をする場合、その作業をnode.jsのコンテナに入れないといけない。その場合、GAEの無料インスタンスに乗らないんですね。それも個人的な都合でネックでした。無料で充分まわるんだもん、業務系のシステムってユーザー数が少ないからw CloudFlareとかに乗せる時も気になってます。今はもう気にしなくて良さそうだけど。
2. Prisma、集約系の機能が弱い
業務系システムやWebサービスの管理画面系になると、GroupByを使わない日は無いと思います。Prismaの独自DSLによって、GroupByの条件を指定するのがとてもだるい。
GroupByした時、JOINできないのも勘弁してほしかった。CTEも怪しい。
TypedSQLがリリースされましたけど、動的な条件組み立てに対応していないので、あまり使い道がない。
3. Prisma、DB独自の関数が利用できない
Prismaが意図的にやっていること。わからんでもない。ブラックボックスになるしね。MySQLだと date_format関数をよく使うので、それが使えないのは困り申した。
Prismaも良いところはあって、クライアントを拡張できること。ページネーション用の関数を生やしたり、RLSに対応したり、クエリを発行する前に where tenant_id = 2みたいなマルチテナントやる時に必須のWHEREを付け加えたり、クエリ発行後にログを出したりイベント発行したりできる。ミドルウェア的な機能が欲しい場合、現状Prisma一択だと思う。
今の自分は、それが必要じゃなかった。
Drizzle with Kysely
自分がメインで使っているのは、Drizzleです。独自スキーマも要らない(全部TypeScriptになる)し、ORM的な使い方もクエリビルダ的な使い方もできる。Prismaのように、1回のクエリ発行でしれっと2発のSQLを投げるみたいなことがない。JOINした時に、user.post.titleみたいな感じで補完されるのすごく便利... マイグレーションもあるし。
Kyselyはすごく好きなクエリビルダなんだけど、SQLの結果セットをそのまま取得する。オブジェクトがネストされない。user.postにはならず、result.title として取得する必要がある。そのため、SELECT句でカラムを指定することが多くて、集約系のQueryじゃない場合は面倒さがある。
でも、SQLの表現力を最大限活かせるのがKyselyのメリットなので、以下のようなQueryを発行したい場合は、drizzleのスキーマからKyselyのスキーマを吐き出して、Kyselyでクエリを書くのがいいなと思っています。
- CASE/WHENのようなSELECT句で加工するようなクエリを書きたい
- DB関数を使いたい
- GroupBy / Havingなどをガッツリ書きたい
- CTEを使いたい
上記のようなケースはあまり無いと思うけど、使いたい時はあります。
DuckDB-wasmとかでローカルにDuckDB入れて、クライアントサイドでダッシュボードの集計をするみたいな時代になっているので、SQLをサーバサイドでゴリゴリマッチョすることは減るのかもしれないけど、Kyselyを使うケースはまだまだ多いでしょう。