🙆‍♂️

オールドスクールな私は Drizzle (drizzle-orm) の夢を見るか?

に公開

はじめに

Node/TypeScriptのORMとしてTypeORMしか使ったことがなかったオールドスクールな私が Prismaデビューしたところ、「Drizzleもあるよ」と通りすがりの親切な方が教えてくれたので、見てみました。

結論から言うと、「Prismaは抽象化しすぎていて複雑なSQLを書こうと思うと結局生SQLになりそうで、ちょっとな、、」と思っていたオールドスクールな私にとって、Drizzle はちょうどよい塩梅な気がしています。

ちなみに、オールドスクールな私が ORMに求める主なものは以下です。

  • 型安全なクエリーが書ける
  • なるべく SQL Like なシンタックス
  • 動的にクエリーが構築できる
  • コードの変更に応じて、DB schema との差分を見て、migration file を作ってくれる(これが無くて、schema-first で schema をベースに型安全なコードを生成してくれるのでも良いですが)

概要比較

項目 Drizzle ORM Prisma
リリース年 2022年 2019年
定義スタイル TypeScriptコードでスキーマ 宣言的スキーマファイル (.prisma)
ワークフロー スキーマ駆動 + SQL志向 スキーマ駆動
真実の源泉 TypeScriptスキーマファイル schema.prisma
アプローチ SQLライク ORM抽象化重視
型安全性 一貫して高い 一貫して高い
クエリビルダー SQL志向 独自のクエリAPI
IDE支援 優秀 優秀
学習コスト 低(SQL知識必要)
ランタイム 軽量 標準的

Drizzle ORM とは?

Drizzle ORMは以下のような特徴を持つTypeScript ORMです:

  • TypeScript-first: スキーマ定義からクエリまで完全にTypeScript
  • SQL-like API: SQLに近い直感的なクエリAPI
  • 軽量: 最小限のランタイムオーバーヘッド
  • マイグレーション機能: 安全なデータベーススキーマ変更

主な特徴

1. TypeScriptネイティブなスキーマ定義

Drizzleでは、スキーマをTypeScriptコードで定義します。

// schema.ts
import { pgTable, serial, text, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

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

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

// リレーション定義
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));

2. SQL-likeなクエリAPI

DrizzleのクエリAPIはSQLに非常に近く、SQLに慣れ親しんだ開発者には直感的です。

import { drizzle } from 'drizzle-orm/node-postgres';
import { eq, and, desc, count } from 'drizzle-orm';
import { users, posts } from './schema';

const db = drizzle(connectionString);

// SELECT文
const allUsers = await db.select().from(users);

// WHERE句
const user = await db
  .select()
  .from(users)
  .where(eq(users.email, 'user@example.com'));

// JOIN
const usersWithPosts = await db
  .select({
    userName: users.name,
    userEmail: users.email,
    postTitle: posts.title,
    postContent: posts.content,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));

// 複雑な条件
const publishedPosts = await db
  .select()
  .from(posts)
  .where(
    and(
      eq(posts.published, true),
      eq(posts.authorId, 1)
    )
  )
  .orderBy(desc(posts.createdAt))
  .limit(10);

3. INSERT/UPDATE/DELETE操作

CRUD操作も直感的に書けます。

// INSERT
const newUser = await db
  .insert(users)
  .values({
    email: 'new@example.com',
    name: 'New User',
  })
  .returning();

// UPDATE
const updatedUser = await db
  .update(users)
  .set({ name: 'Updated Name' })
  .where(eq(users.id, 1))
  .returning();

// DELETE
await db
  .delete(posts)
  .where(eq(posts.id, 1));

// 複数行INSERT
await db
  .insert(posts)
  .values([
    { title: 'Post 1', content: 'Content 1', authorId: 1 },
    { title: 'Post 2', content: 'Content 2', authorId: 1 },
  ]);

4. リレーショナルクエリ

リレーションを使ったクエリも簡潔に書けます。

// ユーザーとその投稿を取得
const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: true,
  },
});

// 特定条件での取得
const userWithPublishedPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      where: eq(posts.published, true),
      orderBy: desc(posts.createdAt),
      limit: 5,
    },
  },
});

マイグレーション

Drizzleは強力なマイグレーション機能を提供します。

# マイグレーションファイル生成
npx drizzle-kit generate:pg

# マイグレーション実行
npx drizzle-kit push:pg

高度な機能

1. 生SQL

import { sql } from 'drizzle-orm';

// 生SQLの実行
const result = await db.execute(
  sql`SELECT COUNT(*) as total FROM users WHERE created_at > ${new Date('2024-01-01')}`
);

// パラメータ化クエリ
const userId = 1;
const userPosts = await db.execute(
  sql`SELECT * FROM posts WHERE author_id = ${userId} AND published = true`
);

2. 集約関数

import { count, avg, sum, max, min } from 'drizzle-orm';

// 集約クエリ
const stats = await db
  .select({
    totalUsers: count(users.id),
    avgPostsPerUser: avg(posts.id),
    latestPost: max(posts.createdAt),
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));

Drizzle Studioによるデータベース管理

Drizzle Studioは、データベースの可視化・編集ツールです。

npx drizzle-kit studio

ブラウザで http://localhost:4983 にアクセスすると、UIでデータベースを操作できます。

実際のプロジェクトでの使用例

Zodとの組み合わせ

import { z } from 'zod';
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { users, posts } from './schema';

// スキーマから自動生成
export const insertUserSchema = createInsertSchema(users);
export const selectUserSchema = createSelectSchema(users);

// カスタマイズ
export const createUserSchema = insertUserSchema.pick({
  email: true,
  name: true,
});

export const updateUserSchema = insertUserSchema
  .pick({
    name: true,
  })
  .partial();

Drizzle ORMの利点

✅ 型安全性: スキーマからクエリまで一貫した型安全性を提供し、コンパイル時にエラーを検出
✅ パフォーマンス: 軽量なランタイムで、最小限のオーバーヘッドでSQLを実行
✅ 学習コスト: SQLに慣れ親しんだ開発者にとって直感的なAPI
✅ 柔軟性: 生SQLとの混在も容易で、複雑なクエリにも対応
✅ 開発者体験: 優れたIDE補完、TypeScript統合、Drizzle Studioによる可視化ツール

参考リンク

Discussion