🏆

【T3 Stack入門】ORM比較(Prisma VS Drizzle)

2024/10/03に公開

はじめに

Typescriptでのフルスタック開発の選択肢として、型安全で先進的な開発ポリシーを持つT3 Stackは、近年フロントエンドを中心に注目を集めています。
そのT3 StackのDB操作を担うORMは、これまでPrismaがラインナップされていましたが、2023年9月に新たな選択肢としてDrizzleが追加されました。
本記事では、PrismaとDrizzleの比較を通じて、それぞれがどのようなケースに適しているかを解説します。

T3 Stackの全体像については以下の記事で解説していますので、興味があればご参照ください。
https://zenn.dev/maicom/articles/efafe3fc3f40e2

ORMとは

まず、PrismaとDrizzleの比較の前に、T3 StackにおいてORMを導入する意味について解説します。
ORMとは、データベースとオブジェクト指向プログラミング言語の間の橋渡しをする技術です。ORMを使えば、開発者はSQLを直接書かずにデータベース操作を行えるようになります。

ORMを導入する意味

ORMを導入の主なメリットを挙げます。

抽象化:
ORMは、データベースの操作(データの更新、取得、削除、管理など)を抽象化することで、データベース特有のSQLクエリを書かずに済むようになります。これにより、データベース操作がシンプルで一貫性のあるコードで実装でき、異なるデータベースを扱う際の移植性も向上します。

メンテナンスのしやすさ:
ORMはデータベーススキーマの変更やマイグレーションをコードベースから管理できるため、バックエンドの知識が少ない開発者でも、データベースの管理が容易です。

型安全性:
PrismaとDrizzleのようなTypeScriptと完全に互換性のあるORMを使用することで、型安全性を保ちながらデータベース操作を行うことができます。SQLを手書きで実装する場合、タイプミスや不正なクエリによるエラーが発生しやすいですが、Prismaはデータベーススキーマに基づいて型を自動生成するため、コードの記述ミスを防ぎ、開発中に型の補完が効くので開発体験が向上します。

PrismaとDrizzleも型安全なTypeScriptのORMという共通点を持ちますが、その開発アプローチには違いがあります。では、PrismaとDrizzleそれぞれの比較に移っていきましょう。

Prismaとは

https://www.prisma.io/

Prismaは、TypeScriptによる型推論が可能なORMです。データベースマイグレーションやデータ管理のためのGUI「Prisma Studio」も機能に含まれています。
Prisma Clientを通じてデータベースにアクセスし、クエリから最適なSQLを生成し、テーブルのカラムやリレーションを型推論に反映します。

Prismaのメリット

Prismaはデータベースの詳細を抽象化することを目指しています。Prismaはドメイン特化言語(DSL)を導入しており、SQLよりも簡潔な記述が可能です。
Prismaを使用することで、SQLの細かい部分を無視してビジネスロジックのコードに集中することができます。これはバックエンドの習熟度に左右されずにフルスタック開発を行うには心強いでしょう。

Prismaのデメリット

DSLを使用することで、基盤となるSQLリクエストの微調整が複雑になり、データベースパフォーマンスに問題が発生した際のデバッグが難しくなる可能性があります。

Drizzleとは

https://orm.drizzle.team/

Drizzleは、TypeScriptベースの軽量なORMで、SQLに近い直感的なクエリ記述が特徴です。リアルタイムでの型生成が可能で、Drizzle Studioを使用して視覚的にデータベースを操作できます。

Drizzleの公式ウェブサイトには「もしSQLを知っていれば、Drizzleも知っているようなものです」とあります。DrizzleはTypeScriptのレイヤーをSQLの上に追加しており、TypeScriptを使用してSQLクエリを書くことができます。

Drizzleのメリット

Drizzleは、SQLスキルを活用して、データベースに送信されるSQLリクエストを微調整できます。Drizzleは型安全なSQLリクエストを可能にし、データベース集約型アプリケーションをスケーリングする際に優れています。TypeScriptを使うため、DSLを学ぶ必要がありません。

データベースで実行されているSQLを正確に理解したい場合、あるいはSQLに精通している場合、Drizzleは最適なORMと言えます。

Drizzleのデメリット

SQLの知識が必要になるため、バックエンドに不慣れなエンジニアにはハードルが高いかも知れません。また、Drizzle ORMは各データベースの特性を活かすため、データベース固有の記述を使用します。裏を返せばデータベースに依存した記述になっているので、データベースを変更する際には修正が必要になります。

PrismaとDrizzleの比較表

技術選定の指標になる主要な項目をピックアップして、両者の違いについて比較表にまとめました。スキーマ定義、型安全性、クエリの記述方法の詳細については後述します。

比較項目 Prisma Drizzle
スキーマ定義 ドメイン特化言語(DSL)で定義 TypeScriptで定義
型安全性 スキーマから生成された型定義 リアルタイムでの型生成
クエリの記述方法 抽象度の高いAPIを提供 SQLライクな記述が可能
データベース対応 PostgreSQL, MySQL, SQLite, MongoDB他 PostgreSQL, MySQL, SQLite
学習コスト 中程度 (DSLの習得が必要) 中程度(SQLに近い構文)
GUIツール あり(Prisma Studio) あり(Drizzle Studio)
コミュニティ/エコシステム 大きく成熟している 成長中だが小さい
ドキュメンテーション 非常に充実している 成長中だが少ない

PrismaとDrizzleの型安全の実現方法の違い

PrismaとDrizzleはどちらもTypeScriptによる型安全性を提供していますが、そのアプローチには違いがあります。

Prismaの型安全性

Prismaは、独自のスキーマファイル(schema.prisma)をもとに、データベースのモデルやリレーションを定義します。

DSLによるスキーマファイルの定義
Prismaでは、専用のドメイン特化言語(DSL)でデータベーススキーマを記述します。このスキーマは、データベースの構造を明確に反映したもので、モデル間のリレーションやフィールドの型を厳密に指定できます。

コマンドによる型生成
npx prisma generateコマンドを実行することで、Prismaはこのスキーマを元に自動的にTypeScriptの型定義ファイルを生成します。生成された型は、Prisma Clientを通してクエリを記述するときに利用され、TypeScriptのコンパイラが型チェックを行います。これにより、データベースクエリの際に型の補完や型エラーの検知が可能になります。

Drizzleの型安全性

一方で、DrizzleはTypeScriptそのものを活用して型安全性を提供します。Prismaとは異なり、TypeScriptの構文に基づいてデータベーススキーマやクエリを定義するため、リアルタイムでの型生成が特徴です。

TypeScriptによるスキーマ定義
Drizzleでは、専用のスキーマ言語を使わず、TypeScriptコードとしてデータベーススキーマを定義します。たとえば、pgTable関数を使用してテーブルの構造を記述し、TypeScriptの型システムに基づいてフィールドの型が決まります。これにより、スキーマ定義そのものが既に型安全なものとなり、他の部分とシームレスに統合されます。

リアルタイム型生成
Prismaのようにコマンドを実行して型を生成するのではなく、Drizzleは定義されたTypeScriptコードをそのまま型システムに反映させます。TypeScriptの型推論機能を利用することで、クエリを記述する段階で型補完がリアルタイムで機能します。クエリの結果や条件式がリアルタイムに型として認識されるため、開発者はすぐに型安全なSQLクエリを記述できます。

PrismaとDrizzleのコード比較

では実際のコード例からその特徴を見てみましょう。ORMでまず触れることになるデータモデリングとクエリをピックアップしてみました。

本記事内のデータモデリング、クエリとは

データモデリングとはデータベースの構造をコード上で定義することを指しています。実際のデータベーステーブルの設計図のようなものです。

クエリとは、データベースからデータを取得したり、データベースにデータを追加・更新・削除したりするための命令のことを指しています。

Prismaのモデリングの特徴

Prismaでは、独自のスキーマ言語を使ってモデルを定義します。例えば、ユーザーモデルは以下のように定義します。

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}

Prismaのモデリングは、独自のスキーマ言語を使用しており、非常に直感的で読みやすい構造になっています。
リレーションシップは@relationアノテーションを用いて明示的に定義され、フィールドの制約(@id, @unique, @default など)も簡潔に表現できます。
この方法により、データベースの構造を視覚的に理解しやすく、複雑なモデル間の関係性も容易に把握することができます。

Drizzleのモデリングの特徴

Drizzleは、TypeScriptを使ってモデルを定義します。これは、通常のTypeScriptコードに近い形で書くことができます。
例えば、先述のPrismaと同じユーザーモデルをDrizzleで定義すると以下のようになります。

import { pgTable, serial, text, boolean, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: text('email').unique().notNull(),
  name: text('name')
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content'),
  published: boolean('published').default(false),
  authorId: integer('author_id').references(() => users.id)
});

Drizzleの定義は、より直接的にSQLの構造を反映しています。
リレーションシップはreferences()メソッドで定義され、SQLの外部キー制約に近い形で表現されます。
この手法により、データベースに精通した開発者にとっては、より直感的にモデルを定義することができます。

Prismaのクエリの特徴

Prismaのクエリは、JavaScriptのメソッドチェーンに似た直感的な方法で書くことができます。例えば、ユーザーとその投稿を取得するクエリは以下のようになります。

// ユーザーとその投稿を取得
const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true }
});

// 特定の条件を満たす投稿を持つユーザーを取得
const usersWithSpecificPosts = await prisma.user.findMany({
  where: {
    posts: {
      some: {
        title: { contains: "Prisma" }
      }
    }
  },
  include: { posts: true }
});

Prismaは、より抽象度の高いAPIを提供し、関連データの取得(include)や条件指定(where句内のsome)が直感的です。

Drizzleのクエリの特徴

Drizzleのクエリは、SQLに非常に近い構文を採用しています。
例えば、ユーザーとその投稿を取得するクエリは以下のようになります。

import { eq } from 'drizzle-orm';

// ユーザーとその投稿を取得
const userWithPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: true
  }
});

// 特定の条件を満たす投稿を持つユーザーを取得
const usersWithSpecificPosts = await db.query.users.findMany({
  where: (users, { exists }) => exists(
    db.select().from(posts).where(
      and(
        eq(posts.userId, users.id),
        like(posts.title, '%Drizzle%')
      )
    )
  ),
  with: {
    posts: true
  }
});

Drizzleは、よりSQLに近い構文を使用し、複雑な条件指定にはサブクエリ(exists)を使用します。これにより、より細かな制御が可能になります。

まとめ

PrismaとDrizzleは、どちらもTypeScript環境でのデータベース操作を支援する強力なORMですが、それぞれ異なるアプローチと特徴を持っています。
それぞれの向いているケースについて以下にまとめます。

Prismaが向いているケース

  • データベースの詳細を抽象化し、SQLの細かい部分を意識せずに開発したい
  • MongoDBをデータベースに使用したい
  • 大規模なエコシステムと成熟したコミュニティを重視している

Drizzleが向いているケース

  • SQLに精通しており、SQLライクな構文で開発したい
  • データベースの細かい制御が必要
  • 軽量で効率的なORMを使いたい

最終的には、プロジェクトの要件、チームのスキルセット、そして求める機能によって選択が変わるため、これらの要素を十分に考慮して最適なORMを選ぶことが重要です。その際にはこの記事がお役に立てれば幸いです。

Discussion