drizzle-migrations
Drizzle ORMを使用したTypeScript/JavaScript向けのマイグレーション優先のデータベース開発ワークフローを提供します。スキーマ定義からマイグレーションファイルの生成・適用までを自動化し、型安全なデータベース管理を効率的に実現します。
description の原文を見る
Migration-first database development workflow using Drizzle ORM for TypeScript/J...
SKILL.md 本文
Drizzle ORM データベースマイグレーション (TypeScript)
TypeScript/JavaScript プロジェクトにおける Drizzle ORM を使用したマイグレーション優先のデータベース開発ワークフロー。
このスキルを使用する場合
以下の場合にこのスキルを使用してください:
- TypeScript/JavaScript プロジェクトで Drizzle ORM を使用している
- データベーススキーマを作成または変更する必要がある
- マイグレーション優先の開発ワークフローを望んでいる
- 新しいデータベーステーブルまたはカラムを設定している
- 環境間でスキーマの一貫性を確保する必要がある
核心原則: マイグレーション優先の開発
重要ルール: スキーマの変更は常にマイグレーションから開始し、コードファーストで進めるべきではありません。
マイグレーション優先の理由
- ✅ SQL マイグレーションは唯一の情報源である
- ✅ 環境間でのスキーマドリフトを防止する
- ✅ ロールバックとバージョニングを可能にする
- ✅ 明示的なスキーマ設計の決定を強制する
- ✅ マイグレーションから TypeScript 型を生成する
- ✅ CI/CD はスキーマ変更を検証できる
アンチパターン (コードファースト)
❌ 誤り: TypeScript スキーマを最初に記述する
// これを最初にしないでください
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
email: text('email').notNull(),
});
正しいパターン (マイグレーション優先)
✅ 正解: 最初に SQL マイグレーションを記述する
-- drizzle/0001_add_users_table.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT NOW()
);
完全なマイグレーションワークフロー
ステップ 1: SQL マイグレーションでスキーマを設計する
説明的な SQL マイグレーションファイルを作成します:
-- drizzle/0001_create_school_calendars.sql
CREATE TABLE school_calendars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
school_id UUID NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
academic_year TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- クエリパフォーマンスのためのインデックスを追加
CREATE INDEX idx_school_calendars_school_id ON school_calendars(school_id);
CREATE INDEX idx_school_calendars_academic_year ON school_calendars(academic_year);
-- 制約を追加
ALTER TABLE school_calendars
ADD CONSTRAINT check_date_range
CHECK (end_date > start_date);
命名規則:
- 連番を使用:
0001_,0002_など - 説明的な名前:
create_school_calendars,add_user_roles - 形式:
XXXX_descriptive_name.sql
ステップ 2: TypeScript 定義を生成する
Drizzle Kit は SQL から TypeScript 型を生成します:
# TypeScript スキーマとスナップショットを生成
pnpm drizzle-kit generate
# または npm を使用
npm run db:generate
このステップで作成されるもの:
- TypeScript スキーマファイル (
drizzle-kit pushを使用する場合) drizzle/meta/XXXX_snapshot.jsonのスナップショットファイル- マイグレーションメタデータ
ステップ 3: スキーマスナップショットを作成する
スナップショットはスキーマドリフト検出を可能にします:
// drizzle/meta/0001_snapshot.json (自動生成)
{
"version": "5",
"dialect": "postgresql",
"tables": {
"school_calendars": {
"name": "school_calendars",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"school_id": {
"name": "school_id",
"type": "uuid",
"notNull": true
}
}
}
}
}
バージョン管理でのスナップショット:
- ✅ スナップショットを git にコミットする
- ✅ CI でドリフト検出を可能にする
- ✅ スキーマ履歴を記録する
ステップ 4: TypeScript スキーマを実装する
SQL マイグレーションに対応する TypeScript スキーマを記述します:
// src/lib/db/schema/school/calendar.ts
import { pgTable, uuid, date, text, timestamp } from 'drizzle-orm/pg-core';
import { schools } from './school';
export const schoolCalendars = pgTable('school_calendars', {
id: uuid('id').primaryKey().defaultRandom(),
schoolId: uuid('school_id')
.notNull()
.references(() => schools.id, { onDelete: 'cascade' }),
startDate: date('start_date').notNull(),
endDate: date('end_date').notNull(),
academicYear: text('academic_year').notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
// 型推論
export type SchoolCalendar = typeof schoolCalendars.$inferSelect;
export type NewSchoolCalendar = typeof schoolCalendars.$inferInsert;
重要なポイント:
- カラム名は SQL と正確に一致:
school_id→'school_id' - TypeScript プロパティ名は camelCase を使用:
schoolId - 制約とインデックスは SQL で定義され、TypeScript では定義されない
- 外部キーは他のテーブルを参照する
ステップ 5: スキーマをドメイン別に整理する
保守性のためにスキーマを構造化します:
src/lib/db/schema/
├── index.ts # すべてのスキーマをエクスポート
├── school/
│ ├── index.ts
│ ├── district.ts
│ ├── holiday.ts
│ ├── school.ts
│ └── calendar.ts
├── providers.ts
├── cart.ts
└── users.ts
index.ts (すべてをエクスポート):
// src/lib/db/schema/index.ts
export * from './school';
export * from './providers';
export * from './cart';
export * from './users';
school/index.ts:
// src/lib/db/schema/school/index.ts
export * from './district';
export * from './holiday';
export * from './school';
export * from './calendar';
ステップ 6: CI に品質チェックを追加する
CI/CD でスキーマの一貫性を検証します:
# .github/workflows/quality.yml
name: Quality Checks
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check database schema drift
run: pnpm drizzle-kit check
- name: Verify migrations (dry-run)
run: pnpm drizzle-kit push --dry-run
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
- name: Run type checking
run: pnpm tsc --noEmit
- name: Lint code
run: pnpm lint
CI チェックの説明:
drizzle-kit check: スナップショットがスキーマと一致することを検証するdrizzle-kit push --dry-run: 適用せずにマイグレーションをテストする- 型チェック: TypeScript がコンパイルされることを確認する
- リント: コードスタイルを強制する
ステップ 7: ステージング環境でテストする
本番環境の前に、ステージング環境でマイグレーションをテストします:
# 1. ステージング環境でマイグレーションを実行
STAGING_DATABASE_URL="..." pnpm drizzle-kit push
# 2. スキーマを確認
pnpm drizzle-kit check
# 3. 関連する API ルートをテスト
curl https://staging.example.com/api/schools/calendars
# 4. データ整合性の問題を確認
# クエリを実行してデータが正しく見えるかを確認
# 5. ログを監視してエラーを確認
# マイグレーション関連のエラーがないかアプリケーションログをチェック
ステージングチェックリスト:
- マイグレーションがエラーなく実行される
- スキーマドリフトチェックが成功する
- 新しいスキーマを使用する API ルートが正常に動作する
- データ整合性の問題がない
- アプリケーションログにエラーが表示されていない
- クエリパフォーマンスが許容範囲内である
一般的なマイグレーションパターン
カラムを追加する
-- drizzle/0005_add_user_phone.sql
ALTER TABLE users
ADD COLUMN phone TEXT;
-- 電話番号でクエリを行う場合はインデックスを追加
CREATE INDEX idx_users_phone ON users(phone);
TypeScript:
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
email: text('email').notNull(),
phone: text('phone'), // 新しいカラム
});
ジャンクションテーブルを作成する
-- drizzle/0006_create_provider_specialties.sql
CREATE TABLE provider_specialties (
provider_id UUID NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
specialty_id UUID NOT NULL REFERENCES specialties(id) ON DELETE CASCADE,
PRIMARY KEY (provider_id, specialty_id)
);
CREATE INDEX idx_provider_specialties_provider ON provider_specialties(provider_id);
CREATE INDEX idx_provider_specialties_specialty ON provider_specialties(specialty_id);
TypeScript:
export const providerSpecialties = pgTable('provider_specialties', {
providerId: uuid('provider_id')
.notNull()
.references(() => providers.id, { onDelete: 'cascade' }),
specialtyId: uuid('specialty_id')
.notNull()
.references(() => specialties.id, { onDelete: 'cascade' }),
}, (table) => ({
pk: primaryKey(table.providerId, table.specialtyId),
}));
カラム型を変更する
-- drizzle/0007_change_price_to_decimal.sql
ALTER TABLE services
ALTER COLUMN price TYPE DECIMAL(10, 2);
TypeScript:
import { decimal } from 'drizzle-orm/pg-core';
export const services = pgTable('services', {
id: uuid('id').primaryKey(),
name: text('name').notNull(),
price: decimal('price', { precision: 10, scale: 2 }).notNull(),
});
制約を追加する
-- drizzle/0008_add_email_constraint.sql
ALTER TABLE users
ADD CONSTRAINT users_email_unique UNIQUE (email);
ALTER TABLE users
ADD CONSTRAINT users_email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$');
設定
drizzle.config.ts
import type { Config } from 'drizzle-kit';
export default {
schema: './src/lib/db/schema/index.ts',
out: './drizzle',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
} satisfies Config;
package.json スクリプト
{
"scripts": {
"db:generate": "drizzle-kit generate:pg",
"db:push": "drizzle-kit push:pg",
"db:studio": "drizzle-kit studio",
"db:check": "drizzle-kit check:pg",
"db:up": "drizzle-kit up:pg"
}
}
マイグレーションテストワークフロー
ローカルテスト
# 1. マイグレーションを作成
echo "CREATE TABLE test (...)" > drizzle/0009_test.sql
# 2. TypeScript を生成
pnpm db:generate
# 3. ローカルデータベースにプッシュ
pnpm db:push
# 4. スキーマを確認
pnpm db:check
# 5. アプリケーションでテスト
pnpm dev
# 影響を受ける機能を手動テスト
# 6. テストを実行
pnpm test
ロールバック戦略
-- drizzle/0010_add_feature.sql (アップマイグレーション)
CREATE TABLE new_feature (...);
-- drizzle/0010_add_feature_down.sql (ダウンマイグレーション)
DROP TABLE new_feature;
ロールバックを適用:
# 手動でダウンマイグレーションを実行
psql $DATABASE_URL -f drizzle/0010_add_feature_down.sql
ベストプラクティス
すべきこと
- ✅ SQL マイグレーションを最初に記述する
- ✅ 説明的なマイグレーション名を使用する
- ✅ 外部キーのインデックスを追加する
- ✅ マイグレーションに制約を含める
- ✅ 本番環境の前にステージング環境でマイグレーションをテストする
- ✅ スナップショットをバージョン管理にコミットする
- ✅ スキーマをドメイン別に整理する
- ✅ CI で
drizzle-kit checkを使用する
すべきでないこと
- ❌ SQL マイグレーションの前に TypeScript スキーマを記述しない
- ❌ ステージング環境でのテストをスキップしない
- ❌ 古いマイグレーションを変更しない (新しいものを作成する)
- ❌ インデックスを追加し忘れない
- ❌ 本番環境で
drizzle-kit pushを使用しない (適切なマイグレーションを使用する) - ❌ スナップショットなしで生成ファイルをコミットしない
トラブルシューティング
スキーマドリフトが検出された
エラー: Schema drift detected
解決策:
# 何が変更されたかを確認
pnpm drizzle-kit check
# スナップショットを再生成
pnpm drizzle-kit generate
# 変更を確認してコミット
git add drizzle/meta/
git commit -m "Update schema snapshots"
マイグレーションがステージング環境で失敗する
エラー: データ制約違反でマイグレーションが失敗する
解決策:
- マイグレーションをロールバック
- データマイグレーションスクリプトを作成
- まずデータマイグレーションを実行
- 次にスキーママイグレーションを実行
-- 最初: データをマイグレーション
UPDATE users SET status = 'active' WHERE status IS NULL;
-- その後: 制約を追加
ALTER TABLE users
ALTER COLUMN status SET NOT NULL;
TypeScript 型がデータベースと同期していない
エラー: TypeScript 型がデータベースと一致していない
解決策:
# すべてを再生成
pnpm db:generate
pnpm tsc --noEmit
# それでも壊れている場合は、スキーマファイルをチェック
# カラム名が SQL と正確に一致していることを確認
関連スキル
universal-data-database-migration- 汎用マイグレーションパターンtoolchains-typescript-data-drizzle- Drizzle ORM の使用パターンtoolchains-typescript-core- TypeScript のベストプラクティスuniversal-debugging-verification-before-completion- 検証ワークフロー
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- bobmatnyc
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/bobmatnyc/claude-mpm-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。