🙆♂️
オールドスクールな私は 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