mcpField
D
drizzle-migrations

drizzle-migrations

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

drizzle-migrations について

TypeScript/JavaScript プロジェクトで Drizzle ORM を使う開発者向けのマイグレーションスキルです。SQL マイグレーションを変更の起点とし、スナップショットと CI チェックでスキーマのずれを検出します。

主な機能

  1. 01SQL マイグレーションを起点に変更
  2. 02マイグレーションから TypeScript 定義を生成
  3. 03スナップショットによるスキーマ差分検出
  4. 04ドメイン別のスキーマ整理
  5. 05CI とステージングで変更を検証

活用シーン

  1. 01データベースにテーブルや列を追加
  2. 02環境間でスキーマの一貫性を維持
  3. 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

生成されるもの:

  1. TypeScript スキーマファイル(drizzle-kit push を使用する場合)
  2. drizzle/meta/XXXX_snapshot.json のスナップショットファイル
  3. マイグレーションメタデータ
ステップ 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"
ステージング環境でマイグレーションが失敗する場合

エラー:データ制約違反によりマイグレーションが失敗します

解決方法:

  1. マイグレーションをロールバックします
  2. データマイグレーションスクリプトを作成します
  3. 先にデータマイグレーションを実行します
  4. 次にスキーママイグレーションを実行します
-- 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 - 検証ワークフロー

コメント