Agentic-creator-os database-migrations
Database schema migration patterns with Prisma and Drizzle ORM. Covers migration strategies, rollbacks, data migrations, and production deployment.
install
source · Clone the upstream repo
git clone https://github.com/frankxai/agentic-creator-os
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/frankxai/agentic-creator-os "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/technical/database-migrations" ~/.claude/skills/frankxai-agentic-creator-os-database-migrations && rm -rf "$T"
manifest:
skills/technical/database-migrations/SKILL.mdsource content
Database Migrations Skill
Manage database schema changes safely with migration tools and best practices.
Prisma Migrations
Initial Setup
# Initialize Prisma npx prisma init # Create migration from schema changes npx prisma migrate dev --name init # Apply migrations in production npx prisma migrate deploy # Reset database (dev only) npx prisma migrate reset
Schema Definition
// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) email String @unique name String? posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([email]) } model Post { id String @id @default(cuid()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId String createdAt DateTime @default(now()) @@index([authorId]) }
Safe Migration Workflow
# 1. Make schema changes in schema.prisma # 2. Create migration (development) npx prisma migrate dev --name add_user_role # 3. Review generated SQL cat prisma/migrations/*/migration.sql # 4. Test in staging DATABASE_URL=$STAGING_URL npx prisma migrate deploy # 5. Deploy to production DATABASE_URL=$PROD_URL npx prisma migrate deploy
Data Migrations
// prisma/migrations/scripts/backfill-user-roles.ts import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function main() { // Backfill default role for existing users await prisma.user.updateMany({ where: { role: null }, data: { role: 'user' }, }); console.log('Backfill complete'); } main() .catch(console.error) .finally(() => prisma.$disconnect());
Drizzle ORM Migrations
Setup
// drizzle.config.ts import type { Config } from 'drizzle-kit'; export default { schema: './src/db/schema.ts', out: './drizzle', driver: 'pg', dbCredentials: { connectionString: process.env.DATABASE_URL!, }, } satisfies Config;
Schema Definition
// src/db/schema.ts import { pgTable, text, timestamp, boolean, index } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), email: text('email').notNull().unique(), name: text('name'), role: text('role').default('user'), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow(), }, (table) => ({ emailIdx: index('email_idx').on(table.email), })); export const posts = pgTable('posts', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), title: text('title').notNull(), content: text('content'), published: boolean('published').default(false), authorId: text('author_id').references(() => users.id), createdAt: timestamp('created_at').defaultNow(), });
Migration Commands
# Generate migration npx drizzle-kit generate:pg # Push schema (dev only, no migration files) npx drizzle-kit push:pg # Apply migrations npx drizzle-kit migrate
Migration Best Practices
1. Additive Changes First
-- SAFE: Add nullable column ALTER TABLE users ADD COLUMN phone TEXT; -- SAFE: Add column with default ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'; -- RISKY: Add NOT NULL without default (locks table, may fail) -- ALTER TABLE users ADD COLUMN required_field TEXT NOT NULL;
2. Multi-Step Breaking Changes
-- Step 1: Add new column (nullable) ALTER TABLE users ADD COLUMN new_email TEXT; -- Step 2: Backfill data (separate migration) UPDATE users SET new_email = email; -- Step 3: Make non-null, add constraint ALTER TABLE users ALTER COLUMN new_email SET NOT NULL; ALTER TABLE users ADD CONSTRAINT users_new_email_unique UNIQUE (new_email); -- Step 4: Drop old column (after app updated) ALTER TABLE users DROP COLUMN email; ALTER TABLE users RENAME COLUMN new_email TO email;
3. Zero-Downtime Patterns
// 1. Expand: Add new column/table // 2. Migrate: Dual-write to old and new // 3. Contract: Remove old column/table // During expand phase - dual write async function createUser(data: UserInput) { return prisma.user.create({ data: { ...data, // Write to both old and new columns email: data.email, emailNew: data.email, }, }); }
Rollback Strategies
Manual Rollback Script
-- prisma/migrations/rollback/20260123_add_role.sql ALTER TABLE users DROP COLUMN IF EXISTS role;
Prisma Rollback
# Mark migration as rolled back (doesn't run SQL) npx prisma migrate resolve --rolled-back 20260123000000_add_role # Then manually apply rollback SQL psql $DATABASE_URL -f prisma/migrations/rollback/20260123_add_role.sql
Production Deployment
CI/CD Pipeline
# .github/workflows/deploy.yml deploy: steps: - name: Run migrations run: npx prisma migrate deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} - name: Deploy app run: vercel --prod
Pre-deployment Checklist
- Migration tested in staging
- Rollback script prepared
- Backup taken before migration
- Migration is additive (if possible)
- No table locks during peak hours
- Monitoring in place
Anti-Patterns
❌ Running
migrate dev in production
❌ Destructive changes without backup
❌ Large data migrations in single transaction
❌ Dropping columns before app update
❌ No rollback plan
✅ Use
migrate deploy in production
✅ Always backup before migration
✅ Batch large data migrations
✅ Expand-migrate-contract pattern
✅ Test rollback procedure