drizzle-orm の使い方

Drizzle ORM package for SQL databases

v0.45.26.8M/週Apache-2.0ORM / DB
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

drizzle-orm の使い方 — TypeScriptファーストなSQL ORM

一言でいうと

Drizzle ORM は、TypeScript ファーストで設計された軽量な SQL ORM です。SQL に近い直感的なクエリビルダーと、完全な型安全性を両立しながら、PostgreSQL・MySQL・SQLite・SingleStore など主要なデータベースをサポートします。


どんな時に使う?

  1. TypeScript プロジェクトで型安全にDBアクセスしたい時 — スキーマ定義からクエリ結果まで、すべてが型推論されるため、ランタイムエラーを大幅に削減できます。
  2. SQL の知識を活かしたい時 — Prisma のような独自クエリ言語ではなく、SQL に近い構文でクエリを組み立てられるため、SQL に慣れたエンジニアの学習コストが低いです。
  3. エッジランタイムやサーバーレス環境で使いたい時 — バンドルサイズが小さく、Cloudflare Workers(D1)、Vercel Edge Functions、Bun などの環境でも動作します。

インストール

Drizzle ORM 本体に加え、使用するデータベースに対応するドライバーパッケージが必要です。

PostgreSQL(node-postgres)の場合

# npm
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg

# yarn
yarn add drizzle-orm pg
yarn add -D drizzle-kit @types/pg

# pnpm
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg

MySQL(mysql2)の場合

npm install drizzle-orm mysql2
npm install -D drizzle-kit

SQLite(better-sqlite3)の場合

npm install drizzle-orm better-sqlite3
npm install -D drizzle-kit @types/better-sqlite3

補足: drizzle-kit はマイグレーション生成・スキーマ管理用の CLI ツールです。開発時に必要になるため、合わせてインストールすることを推奨します。


基本的な使い方

ここでは PostgreSQL + node-postgres を例に、スキーマ定義 → DB接続 → CRUD の一連の流れを示します。

1. スキーマ定義(src/db/schema.ts

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

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  age: integer('age'),
  isActive: boolean('is_active').default(true),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

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

2. DB接続(src/db/index.ts

import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export const db = drizzle(pool, { schema });

3. CRUD 操作

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

// CREATE
const newUser = await db.insert(users).values({
  name: '田中太郎',
  email: 'tanaka@example.com',
  age: 30,
}).returning();

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

// READ(条件付き)
const user = await db
  .select()
  .from(users)
  .where(eq(users.email, 'tanaka@example.com'));

// UPDATE
await db
  .update(users)
  .set({ name: '田中次郎', age: 31 })
  .where(eq(users.id, 1));

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

4. drizzle-kit 設定(drizzle.config.ts

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});
# マイグレーションファイル生成
npx drizzle-kit generate

# マイグレーション実行
npx drizzle-kit migrate

# Drizzle Studio(GUI)起動
npx drizzle-kit studio

よく使う API

1. select + フィルタリング演算子

import { eq, ne, gt, gte, lt, lte, like, ilike, and, or, isNull, inArray } from 'drizzle-orm';

// 複数条件の AND
const activeAdults = await db
  .select()
  .from(users)
  .where(
    and(
      gte(users.age, 18),
      eq(users.isActive, true)
    )
  );

// OR 条件
const result = await db
  .select()
  .from(users)
  .where(
    or(
      like(users.name, '田中%'),
      ilike(users.email, '%example.com')
    )
  );

// IN 句
const specificUsers = await db
  .select()
  .from(users)
  .where(inArray(users.id, [1, 2, 3]));

// NULL チェック
const noAge = await db
  .select()
  .from(users)
  .where(isNull(users.age));

2. JOIN(結合クエリ)

// INNER JOIN
const postsWithAuthors = await db
  .select({
    postTitle: posts.title,
    authorName: users.name,
    authorEmail: users.email,
  })
  .from(posts)
  .innerJoin(users, eq(posts.authorId, users.id));

// LEFT JOIN
const usersWithPosts = await db
  .select()
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));

3. Relational Queries(リレーショナルクエリ API)

スキーマにリレーションを定義すると、Prisma ライクなネスト取得が可能になります。

import { relations } from 'drizzle-orm';

// schema.ts にリレーション定義を追加
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));
// リレーショナルクエリで取得
const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: true,
  },
  where: eq(users.isActive, true),
  limit: 10,
});

// 特定の1件を取得
const singleUser = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      orderBy: (posts, { desc }) => [desc(posts.createdAt)],
      limit: 5,
    },
  },
});

4. トランザクション

const result = await db.transaction(async (tx) => {
  const [newUser] = await tx.insert(users).values({
    name: '佐藤花子',
    email: 'sato@example.com',
    age: 25,
  }).returning();

  await tx.insert(posts).values({
    title: '初めての投稿',
    content: 'こんにちは!',
    authorId: newUser.id,
  });

  return newUser;
});

// ネストされたトランザクション(セーブポイント)
await db.transaction(async (tx) => {
  await tx.insert(users).values({ name: 'A', email: 'a@example.com' });

  try {
    await tx.transaction(async (nestedTx) => {
      await nestedTx.insert(users).values({ name: 'B', email: 'b@example.com' });
      throw new Error('ロールバックテスト');
    });
  } catch {
    // ネストされたトランザクションのみロールバック。ユーザー A は残る
  }
});

5. 集計・ソート・ページネーション

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

// 集計
const stats = await db
  .select({
    totalUsers: count(),
    averageAge: avg(users.age),
  })
  .from(users);

// GROUP BY
const postCountByAuthor = await db
  .select({
    authorId: posts.authorId,
    postCount: count(posts.id),
  })
  .from(posts)
  .groupBy(posts.authorId);

// ORDER BY + LIMIT + OFFSET(ページネーション)
const page = 1;
const pageSize = 20;

const paginatedUsers = await db
  .select()
  .from(users)
  .orderBy(users.createdAt)
  .limit(pageSize)
  .offset((page - 1) * pageSize);

// 生 SQL を混ぜることも可能
const customQuery = await db
  .select({
    id: users.id,
    nameUpper: sql<string>`UPPER(${users.name})`,
  })
  .from(users);

類似パッケージとの比較

特徴Drizzle ORMPrismaKyselyTypeORM
クエリスタイルSQL ライク独自 DSLSQL ライクデコレータ / QueryBuilder
型安全性◎(推論ベース)◎(コード生成)◎(推論ベース)
バンドルサイズ非常に小さい大きい(Engine 含む)小さい大きい
エッジランタイム対応△(一部対応)×
マイグレーションdrizzle-kitprisma migrate別途必要内蔵
リレーショナルクエリ◎(with 構文)◎(include)△(手動 JOIN)
学習コスト低(SQL 知識が活きる)
GUI ツールDrizzle StudioPrisma Studioなしなし
生 SQL サポート◎(sql テンプレート)○($queryRaw

選定の目安:

  • SQL に慣れている+軽量さ重視 → Drizzle ORM
  • スキーマファーストで素早く開発したい → Prisma
  • クエリビルダーだけ欲しい(ORM 不要) → Kysely
  • Java/C# 出身でデコレータに慣れている → TypeORM

注意点・Tips

1. スキーマ定義はデータベース方言ごとに異なる

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

// MySQL
import { mysqlTable, int, varchar } from 'drizzle-orm/mysql-core';

// SQLite
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';

DB を切り替える場合、スキーマ定義の書き直しが必要です。抽象化レイヤーは提供されていません。

2. returning() は PostgreSQL と SQLite のみ

MySQL は INSERT ... RETURNING をサポートしていないため、returning() は使えません。MySQL では $returningId() を使用します。

// MySQL の場合
const result = await db.insert(users).values({ name: '田中' }).$returningId();

3. drizzle-kit pushdrizzle-kit migrate の使い分け

  • push — マイグレーションファイルを生成せず、スキーマを直接 DB に反映。プロトタイピングやローカル開発向け。
  • generatemigrate — マイグレーション SQL を生成してからバージョン管理。本番運用向け。

4. 型の取得(InferSelectModel / InferInsertModel)

import { InferSelectModel, InferInsertModel } from 'drizzle-orm';

// SELECT 結果の型
type User = InferSelectModel<typeof users>;

// INSERT 時の型(default 付きカラムは optional になる)
type NewUser = InferInsertModel<typeof users>;

5. $with によるCTE(共通テーブル式)

const activeUsers = db.$with('active_users').as(
  db.select().from(users).where(eq(users.isActive, true))
);

const result = await db
  .with(activeUsers)
  .select()
  .from(activeUsers);

6. パフォーマンス Tips

  • Prepared Statements を活用すると、同じクエリの繰り返し実行が高速化されます。
const prepared = db
  .select()
  .from(users)
  .where(eq(users.id, sql.placeholder('id')))
  .prepare('get_user_by_id');

const user = await prepared.execute({ id: 1 });
  • **select

比較記事