Drizzle ORM を使い始めたら、SQL と TypeScript の型が自然につながった話

Prisma から Drizzle ORM に乗り換えたところ、SQL に近い直感的な書き心地と TypeScript の型推論精度の高さに驚きました。セットアップ手順と実際に使ってみた感想をまとめます。

🙌 結論から

Drizzle ORM は「SQL っぽい書き心地」と「TypeScript の型安全性」を両立してくれる ORM です!

スキーマを定義すると型が自動で付いてきて、クエリを書いていても「どんな型が返ってくるか」が一目でわかります(^^)

Prisma のような自動生成ステップに依存しない分、動きが予測しやすくて仕事でもかなり使いやすいです。

🤔 Drizzle ORM を選んだ理由

これまで Prisma を使っていたのですが、いくつかの点で不満が出てきました。

  • prisma generate を走らせないと型が更新されない
  • スキーマが Prisma 独自形式なので、SQL の知識と直接対応させにくい
  • エッジ環境(Cloudflare Workers など)でのサポートに制約がある場面があった

Drizzle ORM はこのあたりがかなりスッキリしています。スキーマを TypeScript で直接書くので、生成ステップが不要です!

また、クエリの書き方が SQL に近いため、「どんな SQL が発行されるか」をイメージしながらコードを書けます(`・ω・´)

調べていくうちに「これは合いそう」と感じて、個人プロジェクトで試してみることにしました。

💡 基本的な使い方

drizzle-orm と、接続する DB に合わせたアダプターをインストールします(例: PostgreSQL の場合)。

npm install drizzle-orm postgres
npm install -D drizzle-kit

スキーマはこんな感じで書けます:

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

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

クエリはこう書きます:

// 全件取得
const allUsers = await db.select().from(users);

// 条件付き取得
const user = await db.select().from(users).where(eq(users.id, 1));

SELECT の結果が typeof users.$inferSelect[] 型で返ってくるので、型アサーション不要です!

この「スキーマを書いたらすぐ型が使える」という感覚が、かなり気持ちよかったです(^o^)/

😊 実際に使ってみた感想

個人の Hono + Cloudflare Workers プロジェクトに導入してみました。

Prisma のときは「クエリを書いて → generate して → 型が更新されるのを待つ」という手順がありましたが、Drizzle ではスキーマを書いたら即座に型が使えます!

特によかったのは、エディタの補完がしっかり働く点です。

クエリを書いていて「where の条件にどのカラムが使えるか」「返ってくるオブジェクトのキーは何か」が補完で出てくるのは、かなり快適でした(^^)

マイグレーションも drizzle-kit で管理できて、drizzle-kit generate でマイグレーションファイルを生成 → drizzle-kit migrate で適用、という流れが直感的です。

気になった点は、Prisma に比べてリレーション周りの書き方に若干の慣れが必要な場面があったことです。

ただ、それ以上に「動作が予測しやすい」「生成ファイルへの依存がない」という安心感は相当大きかったです。

🔗 リレーショナルクエリでJOINを書かずに関連取得する

「リレーション周りに慣れが必要」と書いた部分、その後 リレーショナルクエリ(db.query という書き方を知ってからかなり解消されました。

まず relations() でテーブル同士の関係を定義しておきます。

import { relations } from 'drizzle-orm';
import { users, posts } from './schema';

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

これで db.query から、JOINを自分で書かずにネストした関連データを取得できるようになります。

const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: true,
  },
});
// 各 user オブジェクトに posts 配列がそのままぶら下がってくる!

with はネストもできるので、「投稿とそのコメント」のように2階層以上先まで一度に取得することも可能です。

以前 select().from(users).innerJoin(posts, ...) と書いていた部分が、with: { posts: true } の1行で済むようになったのはかなり快適でした(・∀・)

一点だけ注意点があって、リレーショナルクエリの中でカラムを参照するときは、users.id のようにインポートしたテーブルオブジェクトを直接使うのではなく、コールバック引数を経由して参照する必要があります。

const result = await db.query.posts.findMany({
  where: (posts, { eq }) => eq(posts.published, true),
  orderBy: (posts, { desc }) => [desc(posts.createdAt)],
});

最初はこの書き方に慣れず何度かエラーを出しましたが、慣れてしまえば「関連データを含めた読み取り専用のクエリはリレーショナルクエリ、書き込みや複雑な条件は通常のクエリビルダー」と使い分けるのがしっくりきています。

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

✅ まとめ

Drizzle ORM、SQL に慣れているエンジニアにはかなりハマる選択肢だと思います!

特に以下のような方には試してみる価値があります:

  • Prisma の生成ステップが煩わしいと感じている
  • エッジ環境(Cloudflare Workers など)で ORM を使いたい
  • SQL に近い書き方が好みで、型安全も確保したい

Prisma に慣れている方も、一度触ってみると「こういうアプローチもあるんだ」と発見があると思います🙌

※ Drizzle ORMのAPIは今も活発に更新されています。最新情報は Drizzle公式ドキュメント をご確認ください👀