UNPKG

ai-templates

Version:

CLI tool for generating AI templates for different frameworks and tools

599 lines (482 loc) 14.2 kB
# Database Migration Guide - Frontend (React) This command helps you manage database schema changes safely and efficiently. ## Step 1: Migration Setup ### Install Migration Tools ```bash # For SQL databases (PostgreSQL/MySQL) npm install --save knex npm install --save-dev @types/knex # Database drivers npm install --save pg # PostgreSQL npm install --save mysql2 # MySQL npm install --save better-sqlite3 # SQLite # For MongoDB npm install --save migrate-mongo ``` ### Configure Knex ```javascript // knexfile.js module.exports = { development: { client: 'postgresql', connection: { host: process.env.DB_HOST || 'localhost', port: process.env.DB_PORT || 5432, database: process.env.DB_NAME || 'myapp_dev', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD }, pool: { min: 2, max: 10 }, migrations: { directory: './migrations', tableName: 'knex_migrations', extension: 'ts' }, seeds: { directory: './seeds' } }, staging: { client: 'postgresql', connection: process.env.DATABASE_URL, migrations: { directory: './migrations', tableName: 'knex_migrations' } }, production: { client: 'postgresql', connection: { connectionString: process.env.DATABASE_URL, ssl: { rejectUnauthorized: false } }, pool: { min: 2, max: 10 }, migrations: { directory: './migrations', tableName: 'knex_migrations' } } }; ``` ## Step 2: Create Migrations ### Generate Migration File ```bash # Create new migration npx knex migrate:make create_users_table # With timestamp prefix npx knex migrate:make add_email_verification --timestamp # TypeScript migration npx knex migrate:make create_orders_table --ts ``` ### Migration File Structure ```typescript // migrations/20240101120000_create_users_table.ts import { Knex } from 'knex'; export async function up(knex: Knex): Promise<void> { // Create users table await knex.schema.createTable('users', (table) => { table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')); table.string('email', 255).notNullable().unique(); table.string('password_hash', 255).notNullable(); table.string('name', 100).notNullable(); table.enum('role', ['user', 'admin']).defaultTo('user'); table.boolean('email_verified').defaultTo(false); table.timestamp('email_verified_at').nullable(); table.jsonb('metadata').defaultTo('{}'); table.timestamps(true, true); // created_at, updated_at // Indexes table.index('email'); table.index('created_at'); table.index(['role', 'created_at']); }); // Create trigger for updated_at await knex.raw(` CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); `); } export async function down(knex: Knex): Promise<void> { await knex.raw('DROP TRIGGER IF EXISTS update_users_updated_at ON users'); await knex.schema.dropTable('users'); } ``` ### Complex Migration Examples #### Adding Columns ```typescript export async function up(knex: Knex): Promise<void> { await knex.schema.alterTable('users', (table) => { table.string('phone_number', 20).nullable(); table.date('date_of_birth').nullable(); table.string('timezone', 50).defaultTo('UTC'); }); } export async function down(knex: Knex): Promise<void> { await knex.schema.alterTable('users', (table) => { table.dropColumn('phone_number'); table.dropColumn('date_of_birth'); table.dropColumn('timezone'); }); } ``` #### Data Migration ```typescript export async function up(knex: Knex): Promise<void> { // Add new column await knex.schema.alterTable('users', (table) => { table.string('display_name', 100).nullable(); }); // Migrate data const users = await knex('users').select('id', 'first_name', 'last_name'); for (const user of users) { await knex('users') .where('id', user.id) .update({ display_name: `${user.first_name} ${user.last_name}`.trim() }); } // Make column required after data migration await knex.schema.alterTable('users', (table) => { table.string('display_name', 100).notNullable().alter(); }); // Drop old columns await knex.schema.alterTable('users', (table) => { table.dropColumn('first_name'); table.dropColumn('last_name'); }); } ``` ## Step 3: Running Migrations ### Migration Commands ```bash # Run pending migrations npx knex migrate:latest # Run migrations up to specific batch npx knex migrate:up # Rollback last batch npx knex migrate:rollback # Rollback all migrations npx knex migrate:rollback --all # Get migration status npx knex migrate:status # List completed migrations npx knex migrate:list # Run specific migration npx knex migrate:up 20240101120000_create_users_table.ts ``` ### Environment-Specific Migrations ```bash # Development NODE_ENV=development npx knex migrate:latest # Staging NODE_ENV=staging npx knex migrate:latest # Production (be careful!) NODE_ENV=production npx knex migrate:latest ``` ## Step 4: Zero-Downtime Migrations ### Safe Migration Patterns #### Adding Nullable Columns ```typescript // Safe: Add nullable column first export async function up(knex: Knex): Promise<void> { await knex.schema.alterTable('orders', (table) => { table.decimal('tax_amount', 10, 2).nullable(); }); } // Later migration: Populate and make required export async function up(knex: Knex): Promise<void> { // Calculate and populate tax_amount await knex.raw(` UPDATE orders SET tax_amount = total_amount * 0.08 WHERE tax_amount IS NULL `); // Make column required await knex.schema.alterTable('orders', (table) => { table.decimal('tax_amount', 10, 2).notNullable().alter(); }); } ``` #### Renaming Columns (Two-Phase) ```typescript // Phase 1: Add new column and sync data export async function up(knex: Knex): Promise<void> { await knex.schema.alterTable('users', (table) => { table.string('username', 50).nullable(); }); // Copy data await knex.raw('UPDATE users SET username = login_name'); // Add trigger to keep in sync await knex.raw(` CREATE TRIGGER sync_username_to_login_name BEFORE INSERT OR UPDATE ON users FOR EACH ROW EXECUTE FUNCTION sync_columns('username', 'login_name'); `); } // Phase 2: Remove old column (after code deployment) export async function up(knex: Knex): Promise<void> { await knex.raw('DROP TRIGGER sync_username_to_login_name ON users'); await knex.schema.alterTable('users', (table) => { table.dropColumn('login_name'); }); } ``` ## Step 5: Migration Testing ### Test Migration Files ```typescript // tests/migrations/20240101120000_create_users_table.test.ts import { Knex } from 'knex'; import { createTestDb, destroyTestDb } from '../helpers'; describe('Create Users Table Migration', () => { let db: Knex; beforeEach(async () => { db = await createTestDb(); }); afterEach(async () => { await destroyTestDb(db); }); it('should create users table with correct schema', async () => { // Run migration await db.migrate.up(); // Check table exists const hasTable = await db.schema.hasTable('users'); expect(hasTable).toBe(true); // Check columns const columns = await db('users').columnInfo(); expect(columns).toHaveProperty('id'); expect(columns).toHaveProperty('email'); expect(columns).toHaveProperty('created_at'); }); it('should rollback cleanly', async () => { await db.migrate.up(); await db.migrate.down(); const hasTable = await db.schema.hasTable('users'); expect(hasTable).toBe(false); }); }); ``` ### Performance Testing ```typescript // Test migration performance on large datasets export async function testMigrationPerformance(knex: Knex) { console.time('Migration Performance'); // Create test data const testData = Array.from({ length: 100000 }, (_, i) => ({ email: `test${i}@example.com`, name: `Test User ${i}` })); await knex.batchInsert('users', testData, 1000); // Run migration console.time('Actual Migration'); await knex.migrate.up(); console.timeEnd('Actual Migration'); // Verify data integrity const count = await knex('users').count('id').first(); console.log(`Records after migration: ${count}`); console.timeEnd('Migration Performance'); } ``` ## Step 6: Database Backup and Recovery ### Pre-Migration Backup ```bash #!/bin/bash # backup-before-migration.sh TIMESTAMP=$(date +%Y%m%d_%H%M%S) DB_NAME=${DB_NAME:-myapp} BACKUP_DIR="./backups" mkdir -p $BACKUP_DIR echo "Creating backup before migration..." # PostgreSQL pg_dump $DATABASE_URL > "$BACKUP_DIR/pre_migration_${TIMESTAMP}.sql" # MySQL # mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME > "$BACKUP_DIR/pre_migration_${TIMESTAMP}.sql" # Compress backup gzip "$BACKUP_DIR/pre_migration_${TIMESTAMP}.sql" echo "Backup created: $BACKUP_DIR/pre_migration_${TIMESTAMP}.sql.gz" ``` ### Rollback with Restore ```bash #!/bin/bash # rollback-with-restore.sh if [ -z "$1" ]; then echo "Usage: ./rollback-with-restore.sh <backup-file>" exit 1 fi BACKUP_FILE=$1 echo "Rolling back database..." # Drop existing database psql $DATABASE_URL -c "DROP DATABASE IF EXISTS $DB_NAME;" psql $DATABASE_URL -c "CREATE DATABASE $DB_NAME;" # Restore from backup gunzip -c $BACKUP_FILE | psql $DATABASE_URL echo "Database restored from: $BACKUP_FILE" ``` ## Step 7: Migration Documentation ### Migration Log Template ```markdown # Migration: Add User Preferences Table **Date**: 2024-01-15 **Author**: John Doe **Ticket**: JIRA-1234 ## Purpose Add user preferences table to store user-specific settings. ## Changes 1. Create `user_preferences` table 2. Add foreign key to `users` table 3. Create indexes for performance ## Rollback Plan ```sql DROP TABLE IF EXISTS user_preferences; ``` ## Testing - [x] Migration runs successfully - [x] Rollback works correctly - [x] Performance impact acceptable - [x] No breaking changes ## Notes - Default preferences are set via application code - Migration takes ~2 seconds on staging (1M users) ``` ## Step 8: Migration Monitoring ### Add Migration Metrics ```typescript // src/migrations/metrics.ts export async function trackMigration( name: string, fn: () => Promise<void> ): Promise<void> { const start = Date.now(); try { console.log(`Starting migration: ${name}`); await fn(); const duration = Date.now() - start; console.log(`Migration completed: ${name} (${duration}ms)`); // Send metrics await sendMetric('migration.success', { name, duration, environment: process.env.NODE_ENV }); } catch (error) { const duration = Date.now() - start; console.error(`Migration failed: ${name}`, error); await sendMetric('migration.failure', { name, duration, error: error.message, environment: process.env.NODE_ENV }); throw error; } } ``` ## Step 9: CI/CD Integration ### GitHub Actions Migration Check ```yaml # .github/workflows/migration-check.yml name: Migration Check on: pull_request: paths: - 'migrations/**' jobs: test-migrations: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: 18 - name: Install dependencies run: npm ci - name: Run migrations env: DATABASE_URL: postgresql://postgres:postgres@localhost/test run: | npx knex migrate:latest npx knex migrate:rollback --all npx knex migrate:latest - name: Test migration performance run: npm run test:migrations ``` ## Step 10: Production Migration Checklist ```markdown ## Production Migration Checklist ### Pre-Migration - [ ] Migration tested on staging - [ ] Rollback script prepared - [ ] Database backup created - [ ] Team notified of maintenance window - [ ] Migration time estimated ### Migration Execution - [ ] Application in maintenance mode (if required) - [ ] Migration command executed - [ ] Migration verified with status check - [ ] Quick smoke test performed - [ ] Application taken out of maintenance mode ### Post-Migration - [ ] Application functionality verified - [ ] Performance metrics checked - [ ] Error rates monitored - [ ] Database queries performing well - [ ] Migration documented in changelog ### Rollback Criteria - [ ] Migration takes longer than expected (>5 minutes) - [ ] Application errors spike after migration - [ ] Database performance degrades - [ ] Data integrity issues detected ### Emergency Contacts - Database Admin: @dba-team - DevOps Lead: @devops-lead - Product Owner: @product-owner ``` ## Common Migration Patterns ### 1. Adding Indexes Without Downtime ```sql -- PostgreSQL: Create index concurrently CREATE INDEX CONCURRENTLY idx_users_email_created ON users(email, created_at); -- MySQL: Use pt-online-schema-change pt-online-schema-change --alter "ADD INDEX idx_email_created (email, created_at)" D=mydb,t=users ``` ### 2. Large Table Alterations ```typescript // Batch processing for large updates export async function up(knex: Knex): Promise<void> { const batchSize = 1000; let offset = 0; while (true) { const batch = await knex('large_table') .select('id') .orderBy('id') .limit(batchSize) .offset(offset); if (batch.length === 0) break; await knex('large_table') .whereIn('id', batch.map(r => r.id)) .update({ new_column: knex.raw('old_column * 1.1') }); offset += batchSize; // Prevent overwhelming the database await new Promise(resolve => setTimeout(resolve, 100)); } } ```