mcpField
D
drizzle-orm

drizzle-orm

github.com/mindrallyデータ・データベース2881.1k 回インストール3 週間前に更新
リポジトリ

drizzle-orm について

TypeScriptでDrizzle ORMを使う開発ガイドで、スキーマ設計、型付きSQLクエリ、データベース接続、マイグレーションを扱います。データベースを使うアプリケーションの開発者向けに、スキーマのコード管理、コンパイル時の型安全性、効率的なクエリを重視します。

主な機能

  1. 01スキーマとリレーションの定義
  2. 02型付きSQLクエリのパターン
  3. 03PostgreSQLとSQLiteの接続
  4. 04マイグレーションの生成と実行
  5. 05TypeScriptモデル型の推論
  6. 06インデックス、ページネーション、トランザクション

活用シーン

  1. 01型付きデータベーススキーマの定義
  2. 02リレーショナルクエリの実装
  3. 03マイグレーションの生成と適用
  4. 04クエリ性能の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mindrally/skills --skill drizzle-orm

インストール後、Claude Code で/drizzle-ormを入力して読み込まれたか確認します。

詳細

Drizzle ORM 開発ガイドライン

Drizzle ORM、TypeScript、SQL データベース設計に精通しており、型安全性とパフォーマンスを重視しています。

基本原則

  • Drizzle は SQL を重視しています。SQL が分かれば、Drizzle も分かります
  • スキーマをコードで管理することで、唯一の信頼できる情報源として機能します
  • 型安全性はコンパイル時に適用され、実行時より前にエラーを検出できます
  • 軽量で、実行時のオーバーヘッドは最小限です(約 7.4kb min+gzip)
  • サーバーレスに対応しており、Node.js、Bun、Deno、Cloudflare Workers で動作します

スキーマ設計

基本的なテーブル定義
import { pgTable, serial, text, varchar, timestamp, boolean, integer } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  name: text("name"),
  isActive: boolean("is_active").default(true),
  createdAt: timestamp("created_at").defaultNow(),
  updatedAt: timestamp("updated_at").defaultNow(),
});

export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 255 }).notNull(),
  content: text("content"),
  authorId: integer("author_id").references(() => users.id),
  publishedAt: timestamp("published_at"),
  createdAt: timestamp("created_at").defaultNow(),
});
スキーマの構成

スキーマは複数の方法で構成できます。

// Option 1: Single schema.ts file (recommended for smaller projects)
// src/db/schema.ts

// Option 2: Split by domain (recommended for larger projects)
// src/db/schema/users.ts
// src/db/schema/posts.ts
// src/db/schema/index.ts (re-exports all)
命名規則

casing オプションを使用すると、camelCase から snake_case へのマッピングを自動化できます。

import { drizzle } from "drizzle-orm/node-postgres";

const db = drizzle(pool, {
  casing: "snake_case", // Automatically maps camelCase to snake_case
});
リレーションの定義
import { relations } from "drizzle-orm";

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

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));
インデックスの追加
import { pgTable, serial, varchar, index, uniqueIndex } from "drizzle-orm/pg-core";

export const users = pgTable(
  "users",
  {
    id: serial("id").primaryKey(),
    email: varchar("email", { length: 255 }).notNull(),
    name: varchar("name", { length: 255 }),
  },
  (table) => [
    uniqueIndex("email_idx").on(table.email),
    index("name_idx").on(table.name),
  ]
);

データベース接続

node-postgres を使用した PostgreSQL
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 });
better-sqlite3 を使用した SQLite
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import * as schema from "./schema";

const sqlite = new Database("sqlite.db");
export const db = drizzle(sqlite, { schema });
Turso/LibSQL
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import * as schema from "./schema";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

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

クエリのパターン

SELECT クエリ
// Select all columns
const allUsers = await db.select().from(users);

// Select specific columns
const userEmails = await db.select({ email: users.email }).from(users);

// With conditions
import { eq, and, or, gt, like } from "drizzle-orm";

const activeUsers = await db
  .select()
  .from(users)
  .where(eq(users.isActive, true));

const filteredUsers = await db
  .select()
  .from(users)
  .where(
    and(
      eq(users.isActive, true),
      like(users.email, "%@example.com")
    )
  );
リレーショナルクエリ
// Query with relations (requires schema with relations defined)
const usersWithPosts = await db.query.users.findMany({
  with: {
    posts: true,
  },
});

// Nested relations
const postsWithAuthor = await db.query.posts.findMany({
  with: {
    author: {
      columns: {
        id: true,
        name: true,
      },
    },
  },
});
INSERT 操作
// Single insert
const newUser = await db
  .insert(users)
  .values({
    email: "[email protected]",
    name: "John Doe",
  })
  .returning();

// Bulk insert
await db.insert(users).values([
  { email: "[email protected]", name: "User 1" },
  { email: "[email protected]", name: "User 2" },
]);

// Upsert (insert or update on conflict)
await db
  .insert(users)
  .values({ email: "[email protected]", name: "John" })
  .onConflictDoUpdate({
    target: users.email,
    set: { name: "John Updated" },
  });
UPDATE 操作
await db
  .update(users)
  .set({ name: "Jane Doe", updatedAt: new Date() })
  .where(eq(users.id, 1));
DELETE 操作
await db.delete(users).where(eq(users.id, 1));
トランザクション
await db.transaction(async (tx) => {
  const [user] = await tx
    .insert(users)
    .values({ email: "[email protected]", name: "User" })
    .returning();

  await tx.insert(posts).values({
    title: "First Post",
    authorId: user.id,
  });
});

マイグレーション

マイグレーションの生成
# Generate migration based on schema changes
npx drizzle-kit generate

# Apply migrations to database
npx drizzle-kit migrate

# Push schema directly (development only)
npx drizzle-kit push
マイグレーションの設定
// 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!,
  },
});

型安全性のベストプラクティス

スキーマから型を推論する
import { InferSelectModel, InferInsertModel } from "drizzle-orm";

// Infer types from table definitions
export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;

// Use in application code
function createUser(data: NewUser): Promise<User> {
  return db.insert(users).values(data).returning().then((r) => r[0]);
}
厳格な TypeScript 設定

tsconfig.json で strict モードが有効になっていることを確認してください。

{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true
  }
}

パフォーマンスのベストプラクティス

インデックスを適切に使用する

WHERE 句や JOIN で使用するカラムには、必ずインデックスを追加してください。

export const orders = pgTable(
  "orders",
  {
    id: serial("id").primaryKey(),
    userId: integer("user_id").notNull(),
    status: varchar("status", { length: 50 }).notNull(),
    createdAt: timestamp("created_at").defaultNow(),
  },
  (table) => [
    index("user_id_idx").on(table.userId),
    index("status_idx").on(table.status),
    index("created_at_idx").on(table.createdAt),
  ]
);
必要なカラムのみを選択する
// Bad: Fetches all columns
const users = await db.select().from(users);

// Good: Fetches only needed columns
const userNames = await db
  .select({ id: users.id, name: users.name })
  .from(users);
適切なページネーションを使用する
const page = 1;
const pageSize = 20;

const paginatedUsers = await db
  .select()
  .from(users)
  .limit(pageSize)
  .offset((page - 1) * pageSize)
  .orderBy(users.createdAt);
N+1 クエリを避ける
// Bad: N+1 query pattern
const users = await db.select().from(users);
for (const user of users) {
  const posts = await db.select().from(posts).where(eq(posts.authorId, user.id));
}

// Good: Use relational queries or joins
const usersWithPosts = await db.query.users.findMany({
  with: { posts: true },
});

よくある間違いとその回避方法

  1. インデックスを定義しない - 頻繁にクエリされるカラムには、必ずインデックスを追加してください
  2. データを取得しすぎる - 必要なカラムだけを選択してください
  3. 外部キー制約がない - スキーマで適切なリレーションを定義してください
  4. マイグレーションを手動で変更する - drizzle-kit にマイグレーション履歴を管理させてください
  5. トランザクションを使用しない - データの整合性を保つため、関連する操作をトランザクションで囲んでください

コメント