🚀

Drizzle ORM × Claude Code:次世代のTypeScript開発体験

8 か月前
6

🎯 この記事の概要

解決する問題

  • TypeScriptでの型安全なデータベース操作
  • ORMツールの選択に迷っている
  • AI支援開発との相性を知りたい

対象読者

  • TypeScript経験1年以上
  • データベース操作の基本知識
  • 効率的な開発ツールを探している方

前提知識

  • TypeScriptの基本文法
  • SQLの基本概念(SELECT、JOIN等)
  • Node.jsプロジェクトの構築経験

📊 結論・要点

Drizzle ORMをおすすめする理由

  • 完全な型安全性: コンパイル時にSQLエラーを検出
  • SQLライクな直感的記法: 学習コストが低い
  • AI開発との相性: 明示的なコードでClaude Codeが理解しやすい
  • 軽量設計: 最小限のオーバーヘッド

TypeScriptプロジェクトでデータベースを扱う際、Prisma、Supabase-js、TypeORMなど様々な選択肢があります。今回は、Drizzle ORMを使った開発体験と、AI支援開発ツールClaude Codeとの相性の良さについて、実際のプロジェクトでの経験を基に解説します。

💡 Drizzle ORMとは?

Drizzle ORMは、TypeScriptファーストで設計された軽量なORM(Object-Relational Mapping)ツールです。

主な特徴

  • SQLライクな記法: 既存のSQL知識を活かせる直感的なAPI
  • 完全な型安全性: TypeScriptの型システムを活用してコンパイル時エラー検出
  • 軽量設計: 最小限のランタイムオーバーヘッド
  • マルチデータベース対応: PostgreSQL、MySQL、SQLiteをサポート

ORMとは?
ORM(Object-Relational Mapping)は、データベースのテーブルとプログラムのオブジェクトを対応付ける技術です。SQLを直接書く代わりに、プログラミング言語の記法でデータベース操作を行えます。

📊 主要ORMの比較

基本的なクエリの書き方

// Drizzle - SQLに近い直感的な記法 const users = await db .select({ id: users.id, name: users.name, postCount: count(posts.id) }) .from(users) .leftJoin(posts, eq(users.id, posts.userId)) .where(eq(users.isActive, true)) .groupBy(users.id); // Prisma - 独自のオブジェクト記法 const users = await prisma.user.findMany({ where: { isActive: true }, include: { _count: { select: { posts: true } } } }); // Supabase-js - チェーンメソッド const { data } = await supabase .from('users') .select(` id, name, posts(count) `) .eq('is_active', true);

型安全性の比較

特徴DrizzlePrismaSupabase-jsTypeORM
コンパイル時型チェック✅ 完全✅ 完全⚠️ 部分的⚠️ 部分的
スキーマからの型生成✅ TypeScript定義✅ 自動生成⚠️ 手動/生成✅ デコレータ
JOINの型推論✅ 自動✅ 自動❌ 手動⚠️ 部分的
SQLクエリの型安全性✅ ビルダー経由⚠️ Raw SQLは未対応❌ 文字列⚠️ 部分的
実行時の型検証❌ なし✅ あり❌ なし⚠️ 部分的

🚀 Drizzle ORMの実装例

1. スキーマ定義

// schema/users.ts import { pgTable, text, boolean, timestamp, uuid } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), email: text('email').notNull().unique(), name: text('name').notNull(), isActive: boolean('is_active').default(true), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), }); export const posts = pgTable('posts', { id: uuid('id').primaryKey().defaultRandom(), userId: uuid('user_id').notNull().references(() => users.id), title: text('title').notNull(), content: text('content'), published: boolean('published').default(false), createdAt: timestamp('created_at').notNull().defaultNow(), });

2. 複雑なクエリの実装

// 投稿数とともにアクティブユーザーを取得 async function getActiveUsersWithStats() { const result = await db .select({ userId: users.id, userName: users.name, email: users.email, totalPosts: count(posts.id), publishedPosts: count( case_().when(posts.published, 1).else(null) ), latestPostDate: max(posts.createdAt), }) .from(users) .leftJoin(posts, eq(users.id, posts.userId)) .where(eq(users.isActive, true)) .groupBy(users.id) .having(gt(count(posts.id), 0)) .orderBy(desc(count(posts.id))); return result; }

3. トランザクション処理

// ユーザーと初期投稿を同時に作成 async function createUserWithWelcomePost(userData: NewUser) { return await db.transaction(async (tx) => { // ユーザー作成 const [newUser] = await tx .insert(users) .values(userData) .returning(); // ウェルカム投稿を作成 const [welcomePost] = await tx .insert(posts) .values({ userId: newUser.id, title: 'Welcome to our platform!', content: `Hello ${newUser.name}, welcome aboard!`, published: true, }) .returning(); return { user: newUser, post: welcomePost }; }); }

🤖 Claude CodeとDrizzleの相性が良い理由

1. 明示的なコード生成

Claude Codeは、SQLの知識を直接活用してDrizzleのクエリを生成できます:

// Claude Codeへの指示例 "ユーザーの最新10件の投稿を取得するクエリを書いて" // 生成されるコード const recentPosts = await db .select() .from(posts) .where(eq(posts.userId, userId)) .orderBy(desc(posts.createdAt)) .limit(10);

2. 段階的な実装サポート

// Step 1: 基本クエリから開始 const allUsers = await db.select().from(users); // Step 2: 条件を追加 const activeUsers = await db .select() .from(users) .where(eq(users.isActive, true)); // Step 3: JOINを追加 const usersWithPosts = await db .select() .from(users) .leftJoin(posts, eq(users.id, posts.userId)) .where(eq(users.isActive, true)); // Step 4: 集計を追加 const userStats = await db .select({ user: users, postCount: count(posts.id) }) .from(users) .leftJoin(posts, eq(users.id, posts.userId)) .groupBy(users.id);

3. エラーの明確性

// TypeScriptの型エラーが具体的 db.select() .from(users) .where(eq(users.email, 123)); // ❌ Type error: number is not assignable to string // SQLエラーも理解しやすい db.select() .from(users) .where(eq(users.nonExistentColumn, 'value')); // ❌ Property 'nonExistentColumn' does not exist

💡 Drizzleが特に優れているユースケース

1. 複雑なJOINが必要な場合

// 複数テーブルを結合した統計情報の取得 const analytics = await db .select({ date: sql<string>`DATE(${orders.createdAt})`, totalOrders: count(orders.id), uniqueCustomers: countDistinct(orders.customerId), totalRevenue: sum(orderItems.price), avgOrderValue: avg(orderItems.price), }) .from(orders) .leftJoin(orderItems, eq(orders.id, orderItems.orderId)) .leftJoin(customers, eq(orders.customerId, customers.id)) .where(gte(orders.createdAt, lastMonth)) .groupBy(sql`DATE(${orders.createdAt})`);

2. 動的クエリの構築

function buildDynamicQuery(filters: FilterOptions) { let query = db.select().from(products); const conditions = []; if (filters.category) { conditions.push(eq(products.category, filters.category)); } if (filters.minPrice) { conditions.push(gte(products.price, filters.minPrice)); } if (filters.inStock) { conditions.push(gt(products.stock, 0)); } if (conditions.length > 0) { query = query.where(and(...conditions)); } if (filters.sortBy) { query = query.orderBy( filters.sortOrder === 'desc' ? desc(products[filters.sortBy]) : asc(products[filters.sortBy]) ); } return query; }

3. 生SQLが必要な場合

// Window関数を使った高度なクエリ const rankedProducts = await db.execute(sql` WITH RankedProducts AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) as rank FROM products ) SELECT * FROM RankedProducts WHERE rank <= 5 `);

🎯 導入のベストプラクティス

1. プロジェクトのセットアップ

# 必要なパッケージのインストール npm install drizzle-orm postgres npm install -D drizzle-kit @types/pg # 設定ファイルの作成 touch drizzle.config.ts
// drizzle.config.ts import type { Config } from 'drizzle-kit'; export default { schema: './src/db/schema/*', out: './drizzle', driver: 'pg', dbCredentials: { connectionString: process.env.DATABASE_URL!, }, } satisfies Config;

2. 接続設定

// src/db/index.ts import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import * as schema from './schema'; const connectionString = process.env.DATABASE_URL!; const sql = postgres(connectionString); export const db = drizzle(sql, { schema });

3. マイグレーション

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

🚀 まとめ

Drizzle ORMは、以下の特徴により、特にClaude CodeのようなAI支援ツールとの相性が抜群です:

SQLライクな直感的な記法

  • SQLの知識をそのまま活用できる
  • 生成されるクエリが予測可能

完全な型安全性

  • コンパイル時にエラーを検出
  • IDEの補完機能を最大限活用

最小限のオーバーヘッド

  • 薄いラッパーレイヤー
  • 高速な実行速度

柔軟性

  • 複雑なクエリも型安全に記述
  • 生SQLへのエスケープハッチ

次のステップ

  1. Drizzle ORM公式ドキュメントで基本概念を学習
  2. 小規模なプロジェクトで実際に試してみる
  3. Claude Codeと組み合わせた開発フローを体験

特に、複雑なJOINや集計処理が必要なプロジェクトでは、Drizzle ORMの採用により、開発効率の大きな改善が期待できます。

AI支援開発が当たり前になりつつある現在、明示的で予測可能なコードを生成できるDrizzle ORMは、次世代のTypeScript開発における有力な選択肢となるでしょう。

📚 参考資料

0
0
0
Drizzle ORM × Claude Code:次世代のTypeScript開発体験
0
投稿
0
フォロワー
0
いいね

プロパティ

ページ
テクノロジー
TECH