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!