drizzle-migrations について
TypeScript/JavaScript プロジェクトで Drizzle ORM を使う開発者向けのマイグレーションスキルです。SQL マイグレーションを変更の起点とし、スナップショットと CI チェックでスキーマのずれを検出します。
主な機能
- 01SQL マイグレーションを起点に変更
- 02マイグレーションから TypeScript 定義を生成
- 03スナップショットによるスキーマ差分検出
- 04ドメイン別のスキーマ整理
- 05CI とステージングで変更を検証
活用シーン
- 01データベースにテーブルや列を追加
- 02環境間でスキーマの一貫性を維持
- 03マイグレーションによるスキーマ差分を確認・解消
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill drizzle-migrationsインストール後、Claude Code で/drizzle-migrationsを入力して読み込まれたか確認します。
同梱ファイルmetadata.json
詳細
Drizzle ORM データベースマイグレーション(TypeScript)
TypeScript/JavaScript プロジェクト向けの、Drizzle ORM を使用したマイグレーションファーストのデータベース開発ワークフローです。
このスキルを使用するタイミング
次の場合にこのスキルを使用します。
- TypeScript/JavaScript プロジェクトで Drizzle ORM を使用する場合
- データベーススキーマを作成または変更する必要がある場合
- マイグレーションファーストの開発ワークフローを採用したい場合
- 新しいデータベーステーブルやカラムを設定する場合
- 環境間でスキーマの一貫性を確保する必要がある場合
基本原則:マイグレーションファーストの開発
重要なルール:スキーマの変更は必ずマイグレーションから始め、コードファーストでは行いません。
マイグレーションファーストにする理由
- ✅ SQL マイグレーションが唯一の信頼できる情報源になります
- ✅ 環境間のスキーマのずれを防ぎます
- ✅ ロールバックとバージョン管理が可能になります
- ✅ スキーマ設計の判断を明示的に行えます
- ✅ マイグレーションから TypeScript の型を生成できます
- ✅ CI/CD でスキーマ変更を検証できます
アンチパターン(コードファースト)
❌ 誤り:最初に TypeScript スキーマを書く
// DON'T DO THIS FIRST
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()
);
-- Add indexes for query performance
CREATE INDEX idx_school_calendars_school_id ON school_calendars(school_id);
CREATE INDEX idx_school_calendars_academic_year ON school_calendars(academic_year);
-- Add constraints
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 の型を生成します。
# Generate TypeScript schema and snapshots
pnpm drizzle-kit generate
# Or using npm
npm run db:generate
生成されるもの:
- TypeScript スキーマファイル(
drizzle-kit pushを使用する場合) drizzle/meta/XXXX_snapshot.jsonのスナップショットファイル- マイグレーションメタデータ
ステップ 3:スキーマスナップショットを作成する
スナップショットを使うと、スキーマのずれを検出できます。
// drizzle/meta/0001_snapshot.json (auto-generated)
{
"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(),
});
// Type inference
export type SchoolCalendar = typeof schoolCalendars.$inferSelect;
export type NewSchoolCalendar = typeof schoolCalendars.$inferInsert;
重要なポイント:
- カラム名は SQL と完全に一致させます:
school_id→'school_id' - TypeScript のプロパティ名にはキャメルケースを使用します:
schoolId - 制約とインデックスは TypeScript ではなく SQL で定義します
- 外部キーは他のテーブルを参照します
ステップ 5:ドメインごとにスキーマを整理する
保守しやすい構成にします。
src/lib/db/schema/
├── index.ts # Export all schemas
├── 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. Run migration on staging
STAGING_DATABASE_URL="..." pnpm drizzle-kit push
# 2. Verify schema
pnpm drizzle-kit check
# 3. Test affected API routes
curl https://staging.example.com/api/schools/calendars
# 4. Check for data integrity issues
# Run queries to verify data looks correct
# 5. Monitor logs for errors
# Check application logs for migration-related errors
ステージングのチェックリスト:
- マイグレーションがエラーなしで実行される
- スキーマのずれのチェックに合格する
- 新しいスキーマを使用する API ルートが正しく動作する
- データ整合性の問題がない
- アプリケーションログにエラーがない
- クエリのパフォーマンスに問題がない
一般的なマイグレーションパターン
カラムの追加
-- drizzle/0005_add_user_phone.sql
ALTER TABLE users
ADD COLUMN phone TEXT;
-- Add index if querying by phone
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'), // New column
});
中間テーブルの作成
-- 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. Create migration
echo "CREATE TABLE test (...)" > drizzle/0009_test.sql
# 2. Generate TypeScript
pnpm db:generate
# 3. Push to local database
pnpm db:push
# 4. Verify schema
pnpm db:check
# 5. Test in application
pnpm dev
# Manually test affected features
# 6. Run tests
pnpm test
ロールバック戦略
-- drizzle/0010_add_feature.sql (up migration)
CREATE TABLE new_feature (...);
-- drizzle/0010_add_feature_down.sql (down migration)
DROP TABLE new_feature;
ロールバックを適用します:
# Manually run down migration
psql $DATABASE_URL -f drizzle/0010_add_feature_down.sql
ベストプラクティス
推奨事項
- ✅ 最初に SQL マイグレーションを書く
- ✅ 内容がわかるマイグレーション名を使用する
- ✅ 外部キーにインデックスを追加する
- ✅ マイグレーションに制約を含める
- ✅ 本番環境に適用する前に、ステージング環境でマイグレーションをテストする
- ✅ スナップショットをバージョン管理にコミットする
- ✅ ドメインごとにスキーマを整理する
- ✅ CI で
drizzle-kit checkを使用する
避けるべきこと
- ❌ SQL マイグレーションより先に TypeScript スキーマを書かないでください
- ❌ ステージング環境でのテストを省略しないでください
- ❌ 古いマイグレーションを変更しないでください(新しいものを作成してください)
- ❌ インデックスの追加を忘れないでください
- ❌ 本番環境で
drizzle-kit pushを使用しないでください(適切なマイグレーションを使用してください) - ❌ スナップショットなしで生成ファイルをコミットしないでください
トラブルシューティング
スキーマの不整合が検出された場合
エラー:Schema drift detected
解決方法:
# Check what changed
pnpm drizzle-kit check
# Regenerate snapshots
pnpm drizzle-kit generate
# Review changes and commit
git add drizzle/meta/
git commit -m "Update schema snapshots"
ステージング環境でマイグレーションが失敗する場合
エラー:データ制約違反によりマイグレーションが失敗します
解決方法:
- マイグレーションをロールバックします
- データマイグレーションスクリプトを作成します
- 先にデータマイグレーションを実行します
- 次にスキーママイグレーションを実行します
-- First: Migrate data
UPDATE users SET status = 'active' WHERE status IS NULL;
-- Then: Add constraint
ALTER TABLE users
ALTER COLUMN status SET NOT NULL;
TypeScript の型が同期していない場合
エラー:TypeScript の型がデータベースと一致しません
解決方法:
# Regenerate everything
pnpm db:generate
pnpm tsc --noEmit
# If still broken, check schema files
# Ensure column names match SQL exactly
関連するスキル
universal-data-database-migration- 汎用的なマイグレーションパターンtoolchains-typescript-data-drizzle- Drizzle ORM の使用パターンtoolchains-typescript-core- TypeScript のベストプラクティスuniversal-debugging-verification-before-completion- 検証ワークフロー