drizzle-orm
Drizzle ORMを使った開発に関するガイドラインを提供するスキルです。SQLに近い構文を持つ軽量かつ型安全なTypeScript ORMであるDrizzle ORMを活用したデータベース操作やスキーマ定義を支援します。
description の原文を見る
Guidelines for developing with Drizzle ORM, a lightweight type-safe TypeScript ORM with SQL-like syntax
SKILL.md 本文
Drizzle ORM 開発ガイドライン
Drizzle ORM、TypeScript、SQL データベース設計の専門家として、型安全性とパフォーマンスに焦点を当てます。
コア原則
- Drizzle は SQL を重視します - SQL を知っていれば、Drizzle を知っています
- Schema-as-code は単一の情報源として機能します
- 型安全性はコンパイル時に強制され、実行時の前にエラーをキャッチします
- 軽量で、ランタイムオーバーヘッドが最小限です (~7.4kb min+gzip)
- Serverless 対応: 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(),
});
スキーマの構成
スキーマは複数の方法で構成できます:
// オプション 1: 単一の schema.ts ファイル (小規模プロジェクト推奨)
// src/db/schema.ts
// オプション 2: ドメイン別に分割 (大規模プロジェクト推奨)
// src/db/schema/users.ts
// src/db/schema/posts.ts
// src/db/schema/index.ts (すべてを再エクスポート)
命名規則
camelCase を snake_case に自動的にマッピングするために casing オプションを使用します:
import { drizzle } from "drizzle-orm/node-postgres";
const db = drizzle(pool, {
casing: "snake_case", // camelCase を自動的に 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 クエリ
// すべての列を選択
const allUsers = await db.select().from(users);
// 特定の列を選択
const userEmails = await db.select({ email: users.email }).from(users);
// 条件付き
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")
)
);
リレーショナルクエリ
// リレーション付きクエリ (スキーマでリレーションが定義されている必要があります)
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
});
// ネストされたリレーション
const postsWithAuthor = await db.query.posts.findMany({
with: {
author: {
columns: {
id: true,
name: true,
},
},
},
});
INSERT 操作
// 単一挿入
const newUser = await db
.insert(users)
.values({
email: "user@example.com",
name: "John Doe",
})
.returning();
// 一括挿入
await db.insert(users).values([
{ email: "user1@example.com", name: "User 1" },
{ email: "user2@example.com", name: "User 2" },
]);
// Upsert (競合時に挿入または更新)
await db
.insert(users)
.values({ email: "user@example.com", 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: "user@example.com", name: "User" })
.returning();
await tx.insert(posts).values({
title: "First Post",
authorId: user.id,
});
});
マイグレーション
マイグレーションの生成
# スキーマ変更に基づいてマイグレーションを生成
npx drizzle-kit generate
# マイグレーションをデータベースに適用
npx drizzle-kit migrate
# スキーマを直接プッシュ (開発のみ)
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";
// テーブル定義から型を推論
export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;
// アプリケーションコードで使用
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),
]
);
必要な列のみを選択
// 悪い例: すべての列を取得
const users = await db.select().from(users);
// 良い例: 必要な列のみを取得
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 クエリを回避
// 悪い例: N+1 クエリパターン
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));
}
// 良い例: リレーショナルクエリまたは JOIN を使用
const usersWithPosts = await db.query.users.findMany({
with: { posts: true },
});
避けるべき一般的なミス
- インデックスを定義しない - よくクエリされる列には常にインデックスを追加します
- 過度なデータを取得 - 必要な列のみを選択します
- 外部キー制約がない - スキーマで適切なリレーションを定義します
- マイグレーションを手動で変更 - drizzle-kit にマイグレーション履歴を管理させます
- トランザクションを使用しない - データ整合性のために関連する操作をトランザクションでラップします
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。