7.4 KiB
Migration Workflow Guide
Overview
This project uses a TypeScript-based migration system for database schema changes. All migrations are automatically executed when you start the development server.
🚀 Quick Start
Running Development Server with Migrations
npm run dev
This command will:
- ✅ Run all pending migrations automatically
- 🚀 Start the development server with hot reload
Running Migrations Only
npm run migrate
Use this when you only want to apply migrations without starting the server.
📝 Creating New Migrations
Step 1: Create Migration File
Create a new TypeScript file in src/migrations/ with the naming pattern:
YYYYMMDD-descriptive-name.ts
Example: 20251105-add-new-field.ts
Step 2: Migration Template
import { QueryInterface, DataTypes } from 'sequelize';
/**
* Migration: Brief description
* Purpose: Detailed explanation
* Date: YYYY-MM-DD
*/
export async function up(queryInterface: QueryInterface): Promise<void> {
// Add your forward migration logic here
await queryInterface.addColumn('table_name', 'column_name', {
type: DataTypes.STRING,
allowNull: true,
});
console.log('✅ Migration description completed');
}
export async function down(queryInterface: QueryInterface): Promise<void> {
// Add your rollback logic here
await queryInterface.removeColumn('table_name', 'column_name');
console.log('✅ Migration rolled back');
}
Step 3: Register Migration
Add your new migration to src/scripts/migrate.ts:
// 1. Import at the top
import * as m15 from '../migrations/20251105-add-new-field';
// 2. Execute in the run() function
await (m15 as any).up(sequelize.getQueryInterface());
Step 4: Test
npm run migrate
📋 Current Migrations
The following migrations are configured and will run in order:
2025103001-create-workflow-requests- Core workflow requests table2025103002-create-approval-levels- Approval hierarchy structure2025103003-create-participants- Workflow participants2025103004-create-documents- Document attachments20251031_01_create_subscriptions- User subscriptions20251031_02_create_activities- Activity tracking20251031_03_create_work_notes- Work notes/comments20251031_04_create_work_note_attachments- Note attachments20251104-add-tat-alert-fields- TAT alert fields20251104-create-tat-alerts- TAT alerts table20251104-create-kpi-views- KPI database views20251104-create-holidays- Holiday calendar20251104-create-admin-config- Admin configurations20251105-add-skip-fields-to-approval-levels- Skip approver functionality
🔄 Migration Safety Features
Idempotent Migrations
All migrations should be idempotent (safe to run multiple times). Use checks like:
// Check if column exists before adding
const tableDescription = await queryInterface.describeTable('table_name');
if (!tableDescription.column_name) {
await queryInterface.addColumn(/* ... */);
}
// Check if table exists before creating
const tables = await queryInterface.showAllTables();
if (!tables.includes('table_name')) {
await queryInterface.createTable(/* ... */);
}
Error Handling
Migrations automatically:
- ✅ Stop on first error
- ❌ Exit with error code 1 on failure
- 📝 Log detailed error messages
- 🔄 Prevent server startup if migrations fail
🛠️ Common Migration Operations
Adding a Column
await queryInterface.addColumn('table_name', 'new_column', {
type: DataTypes.STRING(100),
allowNull: false,
defaultValue: 'default_value',
comment: 'Column description'
});
Adding Foreign Key
await queryInterface.addColumn('table_name', 'foreign_key_id', {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'referenced_table',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
});
Creating Index
await queryInterface.addIndex('table_name', ['column_name'], {
name: 'idx_table_column',
unique: false
});
// Partial index with WHERE clause
await queryInterface.addIndex('table_name', ['status'], {
name: 'idx_table_active',
where: {
is_active: true
}
});
Creating Table
await queryInterface.createTable('new_table', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
Modifying Column
await queryInterface.changeColumn('table_name', 'column_name', {
type: DataTypes.STRING(200), // Changed from 100
allowNull: true // Changed from false
});
Dropping Column
await queryInterface.removeColumn('table_name', 'old_column');
Raw SQL Queries
await queryInterface.sequelize.query(`
CREATE OR REPLACE VIEW view_name AS
SELECT * FROM table_name WHERE condition
`);
📊 Database Structure Reference
Always refer to backend_structure.txt for the authoritative database structure including:
- All tables and their columns
- Data types and constraints
- Relationships and foreign keys
- Enum values
- Indexes
🚨 Troubleshooting
Migration Fails with "Column Already Exists"
- The migration might have partially run
- Add idempotent checks or manually rollback the failed migration
Server Won't Start After Migration
- Check the migration error in console
- Fix the migration file
- Run
npm run migrateto retry
Need to Rollback a Migration
# Manual rollback (requires implementing down() function)
ts-node src/scripts/rollback.ts
🎯 Best Practices
- Always test migrations on development database first
- Write rollback logic in
down()function - Use descriptive names for migrations
- Add comments explaining the purpose
- Keep migrations small - one logical change per file
- Never modify existing migration files after they run in production
- Use transactions for complex multi-step migrations
- Backup production before running new migrations
📝 Migration Checklist
Before running migrations in production:
- Tested on local development database
- Verified rollback functionality works
- Checked for data loss scenarios
- Reviewed index impact on performance
- Confirmed migration is idempotent
- Updated
backend_structure.txtdocumentation - Added migration to version control
- Registered in
migrate.ts
🔗 Related Files
- Migration Scripts:
src/migrations/ - Migration Runner:
src/scripts/migrate.ts - Database Config:
src/config/database.ts - Database Structure:
backend_structure.txt - Package Scripts:
package.json
💡 Example: Recent Migration
The latest migration (20251105-add-skip-fields-to-approval-levels) demonstrates best practices:
- ✅ Descriptive naming
- ✅ Clear documentation
- ✅ Multiple related columns added together
- ✅ Foreign key relationships
- ✅ Indexed for query performance
- ✅ Includes rollback logic
- ✅ Helpful console messages
Last Updated: November 5, 2025
Migration Count: 14 migrations
Auto-Run: Enabled for npm run dev