How to run migrations in Node.js

Running migrations in Node.js enables version-controlled database schema changes that ensure consistent database structure across development, staging, and production environments. As the creator of CoreUI with extensive Node.js experience since 2014, I’ve implemented migration systems in numerous enterprise applications for safe database evolution and team collaboration. The most reliable approach uses migration frameworks like Sequelize CLI or dedicated migration tools to create, run, and rollback database changes systematically. This method provides database versioning with rollback capabilities while maintaining data integrity and enabling team-wide schema synchronization.

Use Sequelize CLI to create and run migrations for systematic database schema management in Node.js applications.

// Install and setup Sequelize CLI
// npm install --save-dev sequelize-cli
// npx sequelize-cli init

// sequelize-cli configuration in config/config.js
module.exports = {
    development: {
        username: 'root',
        password: 'password',
        database: 'myapp_dev',
        host: '127.0.0.1',
        dialect: 'postgres'
    },
    production: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME,
        host: process.env.DB_HOST,
        dialect: 'postgres'
    }
}

// Create migration file: npx sequelize-cli migration:generate --name create-users-table
// Generated migration file: migrations/20251208120000-create-users-table.js

'use strict'

module.exports = {
    async up(queryInterface, Sequelize) {
        await queryInterface.createTable('Users', {
            id: {
                allowNull: false,
                autoIncrement: true,
                primaryKey: true,
                type: Sequelize.INTEGER
            },
            email: {
                type: Sequelize.STRING,
                allowNull: false,
                unique: true
            },
            firstName: {
                type: Sequelize.STRING,
                allowNull: false
            },
            lastName: {
                type: Sequelize.STRING,
                allowNull: false
            },
            password: {
                type: Sequelize.STRING,
                allowNull: false
            },
            createdAt: {
                allowNull: false,
                type: Sequelize.DATE,
                defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
            },
            updatedAt: {
                allowNull: false,
                type: Sequelize.DATE,
                defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
            }
        })

        // Add indexes
        await queryInterface.addIndex('Users', ['email'])
        await queryInterface.addIndex('Users', ['createdAt'])
    },

    async down(queryInterface, Sequelize) {
        await queryInterface.dropTable('Users')
    }
}

// Create another migration: npx sequelize-cli migration:generate --name add-role-to-users
// migrations/20251208130000-add-role-to-users.js

'use strict'

module.exports = {
    async up(queryInterface, Sequelize) {
        await queryInterface.addColumn('Users', 'role', {
            type: Sequelize.ENUM('admin', 'user', 'moderator'),
            allowNull: false,
            defaultValue: 'user'
        })

        // Update existing users to have default role
        await queryInterface.bulkUpdate('Users', {
            role: 'user'
        }, {})
    },

    async down(queryInterface, Sequelize) {
        await queryInterface.removeColumn('Users', 'role')
    }
}

// Migration runner script: scripts/migrate.js
const { Sequelize } = require('sequelize')
const { Umzug, SequelizeStorage } = require('umzug')

async function runMigrations() {
    const sequelize = new Sequelize(process.env.DATABASE_URL)

    const umzug = new Umzug({
        migrations: {
            glob: 'migrations/*.js',
            resolve: ({ name, path, context }) => {
                const migration = require(path)
                return {
                    name,
                    up: async () => migration.up(context.queryInterface, Sequelize),
                    down: async () => migration.down(context.queryInterface, Sequelize)
                }
            }
        },
        context: { queryInterface: sequelize.getQueryInterface() },
        storage: new SequelizeStorage({ sequelize }),
        logger: console
    })

    try {
        await umzug.up()
        console.log('All migrations completed successfully')
    } catch (error) {
        console.error('Migration failed:', error)
        process.exit(1)
    } finally {
        await sequelize.close()
    }
}

// Package.json scripts
{
    "scripts": {
        "db:migrate": "npx sequelize-cli db:migrate",
        "db:migrate:undo": "npx sequelize-cli db:migrate:undo",
        "db:migrate:undo:all": "npx sequelize-cli db:migrate:undo:all",
        "db:create": "npx sequelize-cli db:create",
        "db:drop": "npx sequelize-cli db:drop"
    }
}

This code demonstrates comprehensive migration setup using Sequelize CLI with proper up/down migration methods, configuration management, and automated migration running. The system tracks migration history, enables rollbacks, and provides consistent schema changes across environments. Migration files include table creation, column additions, index management, and data updates with proper error handling and transaction support.

Best Practice Note:

This is the migration strategy we implement in CoreUI enterprise backend services for safe, version-controlled database evolution. Always test migrations in development, include rollback procedures, and backup production databases before running migrations in production environments.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author