Understanding Models and Migrations in Sequelize

Models: Your Application's View of Data

Think of a model as an architect's blueprint of a building. Just as a blueprint defines the structure and rules for a building, a model defines the structure and rules for your data. Let's explore how models work in practice:

Basic Model Definition


// models/user.js
module.exports = (sequelize, DataTypes) => {
    const User = sequelize.define('User', {
        firstName: {
            type: DataTypes.STRING,
            allowNull: false
        },
        email: {
            type: DataTypes.STRING,
            unique: true,
            validate: {
                isEmail: true
            }
        },
        status: {
            type: DataTypes.ENUM('active', 'inactive'),
            defaultValue: 'active'
        }
    });

    // Define relationships
    User.associate = (models) => {
        User.hasMany(models.Post, {
            foreignKey: 'userId',
            as: 'posts'
        });
    };

    return User;
};
                

Models vs Migrations: Understanding the Difference

Imagine you're writing a book. The model is like your outline - it describes what the book should contain. Migrations are like the editing process - they represent the actual changes you make to transform your draft into the final book.

Models

Models are like living blueprints that your application uses to interact with data:

  • Define the current structure of your data
  • Handle validation and business logic
  • Manage relationships between different types of data
  • Provide an interface for your application code

Migrations

Migrations are like a historical record of how your database has evolved:

  • Record specific changes to your database structure
  • Allow for version control of your database schema
  • Enable collaboration between team members
  • Provide a way to roll back changes if needed

Working with Production Databases

Updating a production database is like performing surgery - it requires careful planning and precise execution. Here's how migrations help:

Adding a New Column


// migrations/YYYYMMDDHHMMSS-add-phone-to-users.js
module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.addColumn('Users', 'phoneNumber', {
            type: Sequelize.STRING,
            allowNull: true
        });
    },
    down: async (queryInterface, Sequelize) => {
        await queryInterface.removeColumn('Users', 'phoneNumber');
    }
};
                

Real-World Scenario: Building an E-commerce Platform

Let's see how models and migrations work together in a practical scenario:

Initial Product Model


// models/product.js
module.exports = (sequelize, DataTypes) => {
    const Product = sequelize.define('Product', {
        name: DataTypes.STRING,
        price: DataTypes.DECIMAL(10, 2),
        stock: DataTypes.INTEGER
    });
    return Product;
};

// Initial migration
// migrations/YYYYMMDDHHMMSS-create-product.js
module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable('Products', {
            id: {
                allowNull: false,
                autoIncrement: true,
                primaryKey: true,
                type: Sequelize.INTEGER
            },
            name: {
                type: Sequelize.STRING
            },
            price: {
                type: Sequelize.DECIMAL(10, 2)
            },
            stock: {
                type: Sequelize.INTEGER
            },
            createdAt: {
                allowNull: false,
                type: Sequelize.DATE
            },
            updatedAt: {
                allowNull: false,
                type: Sequelize.DATE
            }
        });
    },
    down: async (queryInterface, Sequelize) => {
        await queryInterface.dropTable('Products');
    }
};
                

Adding Product Categories Later


// migrations/YYYYMMDDHHMMSS-add-category-to-products.js
module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.addColumn('Products', 'categoryId', {
            type: Sequelize.INTEGER,
            references: {
                model: 'Categories',
                key: 'id'
            },
            onUpdate: 'CASCADE',
            onDelete: 'SET NULL'
        });
    },
    down: async (queryInterface, Sequelize) => {
        await queryInterface.removeColumn('Products', 'categoryId');
    }
};
                

Migration Management

Managing migrations is like maintaining a ship's log - it helps you track where you've been and where you're going:


# View migration history
npx sequelize-cli db:migrate:status

# Roll back all migrations
npx sequelize-cli db:migrate:undo:all

# Roll back to a specific migration
npx sequelize-cli db:migrate:undo --to YYYYMMDDHHMMSS-migration-name.js
                

Best Practices and Tips

When working with models and migrations, consider these guidelines:

Always Version Control Migrations: Think of migrations as your database's history book - each chapter (migration) tells an important part of the story.

Test Migrations: Before applying migrations to production, test them in a development environment - it's like rehearsing a play before the actual performance.

Backup Before Migrating: Always backup your production database before running migrations - it's your safety net, like having a spare parachute when skydiving.

Keep Migrations Small and Focused: Each migration should do one thing well - like having specialized tools in a toolbox rather than a single multi-purpose tool.