关于 drizzle-migrations
面向 TypeScript/JavaScript 项目的 Drizzle ORM 数据库迁移技能,适合需要管理 schema 变更的开发者。它以 SQL 迁移为唯一变更起点,并强调快照和 CI 检查以控制 schema 漂移。
主要功能
- 01以 SQL 迁移优先管理 schema
- 02从迁移生成 TypeScript 定义
- 03使用快照检测 schema 漂移
- 04按领域组织 schema
- 05在 CI 与暂存环境验证变更
适用场景
- 01为数据库新增表或列
- 02在多个环境间保持 schema 一致
- 03检查并处理迁移导致的 schema 漂移
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill drizzle-migrations安装后在 Claude Code 中输入/drizzle-migrations确认已加载。
附带文件metadata.json
详细介绍
Drizzle ORM 数据库迁移(TypeScript)
使用 Drizzle ORM 为 TypeScript/JavaScript 项目采用迁移优先的数据库开发工作流。
何时使用此技能
适用于以下情况:
- 在 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 属性名使用 camelCase:
schoolId - 在 SQL 中而非 TypeScript 中定义约束和索引
- 外键引用其他表
第 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 可以编译
- Lint:执行代码风格规范
第 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- 验证工作流
评论
支持 Markdown