| name | database-migration |
| description | Execute database migrations across ORMs and platforms with zero-downtime strategies, data transformation, and rollback procedures. Use when migrating databases, changing schemas, performing data transformations, or implementing zero-downtime deployment strategies. |
Database Migration
Master database schema and data migrations across ORMs (Sequelize, TypeORM, Prisma), including
rollback strategies and zero-downtime deployments.
When to Use This Skill
- Migrating between different ORMs
- Performing schema transformations
- Moving data between databases
- Implementing rollback procedures
- Zero-downtime deployments
- Database version upgrades
- Data model refactoring
ORM Migrations
Sequelize Migrations
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
email: {
type: Sequelize.STRING,
unique: true,
allowNull: false,
},
createdAt: Sequelize.DATE,
updatedAt: Sequelize.DATE,
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('users');
},
};
TypeORM Migrations
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateUsers1701234567 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'users',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
{
name: 'email',
type: 'varchar',
isUnique: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('users');
}
}
Prisma Migrations
// schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
createdAt DateTime @default(now())
}
// Generate migration: npx prisma migrate dev --name create_users
// Apply: npx prisma migrate deploy
Schema Transformations
Adding Columns with Defaults
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'status', {
type: Sequelize.STRING,
defaultValue: 'active',
allowNull: false,
});
},
down: async (queryInterface) => {
await queryInterface.removeColumn('users', 'status');
},
};
Renaming Columns (Zero Downtime)
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'full_name', {
type: Sequelize.STRING,
});
await queryInterface.sequelize.query('UPDATE users SET full_name = name');
},
down: async (queryInterface) => {
await queryInterface.removeColumn('users', 'full_name');
},
};
module.exports = {
up: async (queryInterface) => {
await queryInterface.removeColumn('users', 'name');
},
down: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'name', {
type: Sequelize.STRING,
});
},
};
Changing Column Types
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'age_new', {
type: Sequelize.INTEGER,
});
await queryInterface.sequelize.query(`
UPDATE users
SET age_new = CAST(age AS INTEGER)
WHERE age IS NOT NULL
`);
await queryInterface.removeColumn('users', 'age');
await queryInterface.renameColumn('users', 'age_new', 'age');
},
down: async (queryInterface, Sequelize) => {
await queryInterface.changeColumn('users', 'age', {
type: Sequelize.STRING,
});
},
};
Data Transformations
Complex Data Migration
module.exports = {
up: async (queryInterface, Sequelize) => {
const [users] = await queryInterface.sequelize.query('SELECT id, address_string FROM users');
for (const user of users) {
const addressParts = user.address_string.split(',');
await queryInterface.sequelize.query(
`UPDATE users
SET street = :street,
city = :city,
state = :state
WHERE id = :id`,
{
replacements: {
id: user.id,
street: addressParts[0]?.trim(),
city: addressParts[1]?.trim(),
state: addressParts[2]?.trim(),
},
},
);
}
await queryInterface.removeColumn('users', 'address_string');
},
down: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'address_string', {
type: Sequelize.STRING,
});
await queryInterface.sequelize.query(`
UPDATE users
SET address_string = CONCAT(street, ', ', city, ', ', state)
`);
await queryInterface.removeColumn('users', 'street');
await queryInterface.removeColumn('users', 'city');
await queryInterface.removeColumn('users', 'state');
},
};
Rollback Strategies
Transaction-Based Migrations
module.exports = {
up: async (queryInterface, Sequelize) => {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.addColumn(
'users',
'verified',
{ type: Sequelize.BOOLEAN, defaultValue: false },
{ transaction },
);
await queryInterface.sequelize.query(
'UPDATE users SET verified = true WHERE email_verified_at IS NOT NULL',
{
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
down: async (queryInterface) => {
await queryInterface.removeColumn('users', 'verified');
},
};
Checkpoint-Based Rollback
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.query('CREATE TABLE users_backup AS SELECT * FROM users');
try {
await queryInterface.addColumn('users', 'new_field', {
type: Sequelize.STRING,
});
const [result] = await queryInterface.sequelize.query(
'SELECT COUNT(*) as count FROM users WHERE new_field IS NULL',
);
if (result[0].count > 0) {
throw new Error('Migration verification failed');
}
await queryInterface.dropTable('users_backup');
} catch (error) {
await queryInterface.sequelize.query('DROP TABLE users');
await queryInterface.sequelize.query('CREATE TABLE users AS SELECT * FROM users_backup');
await queryInterface.dropTable('users_backup');
throw error;
}
},
};
Zero-Downtime Migrations
Blue-Green Deployment Strategy
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('users', 'email_new', {
type: Sequelize.STRING,
});
},
};
module.exports = {
up: async (queryInterface) => {
await queryInterface.sequelize.query(`
UPDATE users
SET email_new = email
WHERE email_new IS NULL
`);
},
};
module.exports = {
up: async (queryInterface) => {
await queryInterface.removeColumn('users', 'email');
},
};
Cross-Database Migrations
PostgreSQL to MySQL
module.exports = {
up: async (queryInterface, Sequelize) => {
const dialectName = queryInterface.sequelize.getDialect();
if (dialectName === 'mysql') {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
data: {
type: Sequelize.JSON,
},
});
} else if (dialectName === 'postgres') {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
data: {
type: Sequelize.JSONB,
},
});
}
},
};