Up/Down Seeders Tutorial

Welcome to this hands-on tutorial on creating and managing up and down seeders using Sequelize! Imagine you are setting up a new garden where you want to plant specific flowers (data) in well-defined beds (tables). Seeder files provide the instructions on what to plant, and just like a gardener, you might sometimes need to remove or replant your seeds if things go wrong.

In this lesson, you’ll learn how to define the "up" and "down" functions in seeder files, the differences between using the queryInterface methods and using Model.bulkCreate, and how to run and undo seeders via the command-line interface.

Creating Your Seeder Files

To get started, you need to generate a seeder file using the Sequelize CLI. This file will contain two main functions: an up function to insert seed data and a down function to remove that data.

Run this command in your terminal:

npx sequelize seed:generate --name <name_of_seed>

Replace <name_of_seed> with a descriptive name, such as demo-users. The command creates a seeder file with a timestamp prefix (e.g., 20230412123456-demo-users.js) in the seeders folder.

Understanding the Seeder File Structure

Open the generated seeder file. You will see two callback functions:

Here is an example of what the file might look like:

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    /**
     * Add seed commands here.
     *
     * Example:
     * await queryInterface.bulkInsert('People', [{
     *   name: 'John Doe',
     *   isBetaMember: false,
     *   createdAt: new Date(),
     *   updatedAt: new Date()
     * }], {});
     */
  },

  down: async (queryInterface, Sequelize) => {
    /**
     * Add commands to revert seed here.
     *
     * Example:
     * await queryInterface.bulkDelete('People', null, {});
     */
  }
};
  

In this file, queryInterface.bulkInsert is used to insert an array of objects representing your seed data, and queryInterface.bulkDelete is used to remove that data. Notice that when using bulkInsert, you must manually add createdAt and updatedAt timestamps if your table requires them.

Limitations of queryInterface.bulkInsert

While queryInterface.bulkInsert is useful, it does not run model-level validations and does not automatically set timestamps. For example, if your model expects a boolean value for isBetaMember but receives a string, bulkInsert will not catch the error.

// This will NOT throw an error even if validations exist in the model:
await queryInterface.bulkInsert('People', [
  {
    name: 'John Doe',
    isBetaMember: "yes", // should be boolean
    createdAt: new Date(),
    updatedAt: new Date()
  }
], {});
  

Using Model.bulkCreate Instead

To overcome these limitations, the recommended approach is to use Model.bulkCreate instead. This method runs all model-level validations and automatically handles timestamps if configured. Think of it as using a smart planter that checks the quality of your seeds before planting them.

Here’s how you can do it:

// Import the model (e.g., Person) from your models folder
const { Person } = require('../db/models');

// Use bulkCreate to insert seed data with validations
await Person.bulkCreate([
  {
    name: 'John Doe',
    isBetaMember: false
  }
], { validate: true });
  

With validate: true, Sequelize will run all validations defined in the model, ensuring data integrity.

Committing and Running Seeder Files

Creating a seeder file doesn’t automatically insert data into your database. Once you have defined your seed data in the up function, you need to execute the seed command:

npx dotenv sequelize db:seed:all

This command runs all your seed files and populates your database. If you need to run a specific seed file, you can specify it:

npx dotenv sequelize db:seed --seed <name_of_seeder>

Reverting a Seed

Just as migrations have a down function to undo changes, seeders have a down function to remove the inserted data. In your seeder file, you might have:

'use strict';
const { Person } = require('../db/models');

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await Person.bulkCreate([
      {
        name: 'John Doe',
        isBetaMember: false
      }
    ], { validate: true });
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('People', {
      name: 'John Doe',
      isBetaMember: false
    });
  }
};
  

To undo all seeds, run:

npx dotenv sequelize db:seed:undo:all

Or to undo a specific seed:

npx dotenv sequelize db:seed:undo --seed <name_of_seeder>

Real-World Analogy

Think of your database as a newly tilled field and seeder files as instructions for planting crops. The up function is when you plant your seeds (insert data), and the down function is like pulling the crops out if they don’t grow correctly or need to be replaced. Using Model.bulkCreate instead of the more basic method ensures that each seed is checked for quality before being planted, resulting in a healthy, robust field.

Key Takeaways

Conclusion

In this tutorial, you learned how to create up and down seeders in Sequelize to populate and depopulate your database. Seeders are essential for testing and initializing your database with consistent, quality data. By using Model.bulkCreate, you can ensure that the data adheres to your model's validations, while the down function provides a safe way to revert your seed data if needed.

With this knowledge, you can confidently manage your seed data, keeping your database robust and reliable throughout your application’s development lifecycle. Happy seeding!