Understanding Up/Down Seeders in Sequelize: A Comprehensive Guide

Learn how to effectively manage and control your database's initial data

Understanding Up/Down Seeders

Imagine you're preparing to open a new bookstore. The "up" seeder is like your initial stock order - it's the process of filling your shelves with books. The "down" seeder is like having a detailed return slip that lets you undo that stock order if needed. Together, these processes give you complete control over your inventory management.

Let's break this down further using our bookstore analogy:

The "up" process is like receiving a shipment:

- You know exactly what books you're adding

- You have a specific place for each book

- You keep track of when each book arrived

The "down" process is like processing returns:

- You know exactly which books to remove

- You can verify what you're removing matches what you added

- You can restore your inventory to its previous state

Creating Your First Up/Down Seeder

Let's start by creating a complete seeder for our bookstore example. We'll explore both Model.bulkCreate (recommended) and queryInterface.bulkInsert approaches to understand their differences and use cases.

Using Model.bulkCreate (Recommended Approach)


// Import our models at the top
const { Book, Author } = require('../models');

module.exports = {
    up: async (queryInterface, Sequelize) => {
        // First, create our authors
        const authors = await Author.bulkCreate([
            {
                name: 'Jane Austen',
                birthYear: 1775,
                biography: 'English novelist known for romantic fiction'
            },
            {
                name: 'George Orwell',
                birthYear: 1903,
                biography: 'English novelist known for political fiction'
            }
        ], { validate: true }); // Enable model validations

        // Now create books associated with these authors
        await Book.bulkCreate([
            {
                title: 'Pride and Prejudice',
                authorId: authors[0].id,
                publishYear: 1813,
                genre: 'Romance',
                price: 15.99,
                inStock: true
            },
            {
                title: '1984',
                authorId: authors[1].id,
                publishYear: 1949,
                genre: 'Dystopian',
                price: 14.99,
                inStock: true
            }
        ], { 
            validate: true,
            // We can add additional options for more control
            individualHooks: true  // Run hooks for each record
        });
    },

    down: async (queryInterface, Sequelize) => {
        // Remove in reverse order to respect foreign key constraints
        await Book.destroy({
            where: {
                title: ['Pride and Prejudice', '1984']
            }
        });

        await Author.destroy({
            where: {
                name: ['Jane Austen', 'George Orwell']
            }
        });
    }
};
                

Notice how we're using Model.bulkCreate which automatically handles timestamps and validates our data according to our model definitions. This is much safer than using raw queryInterface methods.

Understanding the Differences: bulkCreate vs bulkInsert

Let's explore why Model.bulkCreate is generally preferred over queryInterface.bulkInsert by comparing them directly:

Same Data, Different Approaches


// Approach 1: queryInterface.bulkInsert
module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.bulkInsert('Books', [
            {
                title: 'Pride and Prejudice',
                publishYear: 1813,
                price: '15.99',  // Note: This string might cause issues
                createdAt: new Date(),  // Must manually set timestamps
                updatedAt: new Date()
            }
        ]);
    }
};

// Approach 2: Model.bulkCreate
const { Book } = require('../models');

module.exports = {
    up: async (queryInterface, Sequelize) => {
        await Book.bulkCreate([
            {
                title: 'Pride and Prejudice',
                publishYear: 1813,
                price: 15.99  // Will be properly typed based on model
                // No need to set timestamps manually
            }
        ], { 
            validate: true,
            // Additional helpful options
            returning: true,  // Get back the created records
            logging: console.log  // See the generated SQL
        });
    }
};
                

The Model.bulkCreate approach provides several advantages:

1. Automatic timestamp handling

2. Model validation enforcement

3. Proper data type conversion

4. Hook execution if needed

Advanced Seeding Patterns

Let's explore some more sophisticated seeding scenarios you might encounter in real applications:

Handling Related Data with References


const { User, Order, Product } = require('../models');

module.exports = {
    up: async (queryInterface, Sequelize) => {
        // Create test users first
        const users = await User.bulkCreate([
            {
                email: 'customer1@example.com',
                name: 'John Doe'
            },
            {
                email: 'customer2@example.com',
                name: 'Jane Smith'
            }
        ], { validate: true });

        // Create some products
        const products = await Product.bulkCreate([
            {
                name: 'Premium Widget',
                price: 29.99
            },
            {
                name: 'Basic Widget',
                price: 19.99
            }
        ], { validate: true });

        // Create orders referencing both users and products
        await Order.bulkCreate([
            {
                userId: users[0].id,
                productId: products[0].id,
                quantity: 2,
                totalPrice: products[0].price * 2
            },
            {
                userId: users[1].id,
                productId: products[1].id,
                quantity: 1,
                totalPrice: products[1].price
            }
        ], { validate: true });
    },

    down: async (queryInterface, Sequelize) => {
        // Remember: Remove in reverse order of creation
        await Order.destroy({ where: {} });
        await Product.destroy({ where: {} });
        await User.destroy({ where: {} });
    }
};
                

Best Practices and Common Patterns

Through experience with seeding data, certain patterns have emerged as best practices:

Creating Reusable Seed Data


// seedData/index.js
const users = [
    {
        email: 'test1@example.com',
        name: 'Test User 1'
    },
    {
        email: 'test2@example.com',
        name: 'Test User 2'
    }
];

const products = [
    {
        name: 'Product 1',
        price: 19.99
    },
    {
        name: 'Product 2',
        price: 29.99
    }
];

module.exports = {
    users,
    products
};

// In your seeder file
const seedData = require('../seedData');

module.exports = {
    up: async (queryInterface, Sequelize) => {
        await User.bulkCreate(seedData.users, { validate: true });
        await Product.bulkCreate(seedData.products, { validate: true });
    }
};
                

Handling Errors Gracefully


module.exports = {
    up: async (queryInterface, Sequelize) => {
        try {
            await sequelize.transaction(async (t) => {
                const users = await User.bulkCreate(seedData.users, { 
                    validate: true,
                    transaction: t 
                });
                
                const products = await Product.bulkCreate(seedData.products, { 
                    validate: true,
                    transaction: t
                });
            });
        } catch (error) {
            console.error('Seeding failed:', error);
            throw error; // Rethrow to trigger the migration failure
        }
    }
};
                

Running and Managing Seeds

Understanding how to run and manage your seeds is crucial for effective development:


# Run all seeds
npx dotenv sequelize db:seed:all

# Run a specific seed
npx dotenv sequelize db:seed --seed 20250202000000-demo-users.js

# Undo all seeds
npx dotenv sequelize db:seed:undo:all

# Undo a specific seed
npx dotenv sequelize db:seed:undo --seed 20250202000000-demo-users.js
                

Always remember to run seeds in the correct order to respect data dependencies!