ai-templates
Version:
CLI tool for generating AI templates for different frameworks and tools
599 lines (482 loc) • 14.2 kB
Markdown
# 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));
}
}
```