Managing Sequelize Migrations
Understanding the Problem
Database migrations are like chapters in your database's story. Each migration represents a change to your database structure, and sometimes we need to move backward or forward in this story. Just as a writer might need to revise earlier chapters of their book, developers often need to undo or redo database changes.
Think of migrations as a time machine for your database. We need to learn how to:
- Move forward in time (running migrations)
- Move backward one step (undoing the last migration)
- Move backward to a specific point (undoing multiple migrations)
- Move forward again (redoing migrations)
In our specific case, we have five migration files that create different tables: Colors, Shapes, Cats, Users, and Games. We need to understand how to manage these migrations effectively, including how to run them, undo them selectively, and redo them when needed.
Devising a Plan
- Run all pending migrations to set up our database tables
- Learn how to undo the most recent migration
- Practice redoing migrations that were undone
- Understand how to undo all migrations
- Master the technique of undoing to a specific migration
Carrying Out the Plan
Step 1: Running All Migrations
Let's start by running all our migrations. This will create all our database tables in the correct order:
npx dotenv sequelize db:migrate
After running this command, Sequelize will execute the 'up' function in each migration file in chronological order:
// Example of what happens when migrations run
// First: 20211008201504-create-color.js creates Colors table
// Then: 20211008225744-create-shapes.js creates Shapes table
// Then: 20211008225902-create-cats.js creates Cats table
// Then: 20211008225940-create-users.js creates Users table
// Finally: 20211008225957-create-games.js creates Games table
Step 2: Undoing the Last Migration
To undo just the most recent migration (in our case, the Games table), we use:
npx dotenv sequelize db:migrate:undo
This command will execute the 'down' function of our last migration:
// From 20211008225957-create-games.js
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Games')
}
Step 3: Redoing Migrations
To reapply migrations that were undone, we simply run the migrate command again:
npx dotenv sequelize db:migrate
Sequelize keeps track of which migrations have been run in a special table called 'SequelizeMeta'. When you run migrations, it checks this table to know which migrations need to be executed.
Step 4: Undoing All Migrations
To undo all migrations and start fresh:
npx dotenv sequelize db:migrate:undo:all
Step 5: Undoing to a Specific Migration
To undo migrations up to a specific file, we use the --to flag with dotenv-cli:
npx dotenv sequelize -- db:migrate:undo:all --to 20211008225902-create-cats.js
Notice the extra -- after dotenv. This is crucial for passing flags to the underlying sequelize command.
Looking Back and Learning More
Understanding Migration Order
Migrations run in chronological order based on their timestamp prefixes. This is why our migration files start with numbers like '20211008201504'. When running migrations:
- The 'up' functions run from oldest to newest
- The 'down' functions run from newest to oldest
The SequelizeMeta Table
Sequelize uses a special table called SequelizeMeta to track which migrations have been run. Think of it as a checklist where Sequelize marks off each completed migration. This is how it knows:
- Which migrations still need to be run
- Which migrations to undo when rolling back
- Where to stop when undoing to a specific point
Real-World Applications
Understanding migration management is crucial in real-world scenarios. Here are some common use cases:
Development Scenarios
- Rolling back a migration that introduced a bug
- Testing different database structures during development
- Synchronizing database changes across a team
Production Scenarios
- Safely updating database structure in production
- Rolling back problematic changes quickly
- Managing database versions across different environments
Best Practices
When working with migrations, keep these principles in mind:
Migration Design
- Always test migrations in development first
- Make sure 'down' functions properly reverse 'up' functions
- Keep migrations small and focused
- Add clear comments explaining complex changes
Migration Management
- Never modify existing migrations that have been shared with others
- Create new migrations for additional changes
- Back up your database before running migrations in production
- Document any manual steps required alongside migrations
Troubleshooting Common Issues
Here are solutions to common migration challenges:
- Migration fails to undo: Check that your 'down' function properly reverses all changes made in the 'up' function
- Migration sequence errors: Ensure your migrations don't have conflicting timestamps
- Partial migration completion: Use transactions to ensure all-or-nothing migration execution