Hello! Welcome to the final lesson in our module on Database Schema Design & Migrations.
In our previous lesson, we meticulously built the "scaffolding" for our database. We created the tables, defined their columns, and established the critical relationships between them using foreign keys and indexes. Our database schema is now robust and optimized, but it's also completely empty.
This lesson bridges the gap between an empty schema and a functional development environment. Our goal is to seed the database with realistic test data using model factories and seeders. By the end of this session, you'll be able to:
- Generate large amounts of fake, yet realistic, data for any model using Model Factories.
- Control and orchestrate the data population process using Database Seeders.
- Seamlessly seed complex relationships, ensuring your test data mirrors a real-world application.
- Reset and repopulate your entire database with a single command.
This is an essential skill for efficient development, allowing you to test features, build UIs, and optimize queries without manually entering data.
1. The Tools of the Trade: Factories and Seeders
To populate our database, Laravel provides two distinct but related tools:
- Model Factories: Think of a factory as a blueprint or a recipe for a single model instance. It defines what a fake
UserorProductshould look like, using the Faker library to generate realistic-looking data (names, emails, prices, etc.). - Database Seeders: A seeder is the script that executes these blueprints. It's the "chef" that takes the recipes from the factories and populates the database tables with data.
Let's start with the blueprints.
2. Model Factories: Crafting Realistic Data
A model factory is a class that corresponds to a single Eloquent model. Its primary job is to return an array defining the model's default attributes.
30 Days to Learn Laravel, Ep 10 - Model Factories
To begin, let's get a clear understanding of what model factories are and why they are so valuable in a typical development workflow. This video from Laracasts provides a perfect introduction.
Watch the first 2 minutes of the video (00:09 - 02:00). Pay close attention to the two main use cases mentioned: setting up a local development environment and preparing for automated tests.
Creating and Using a Factory
To use factories, your model must use the Illuminate\Database\Eloquent\Factories\HasFactory trait. Artisan-generated models include this by default.
You can create a new factory using the make:factory Artisan command:
php artisan make:factory ProductFactory --model=Product
This creates a database/factories/ProductFactory.php file. Inside, the definition() method is where you'll define the fake data using the fake() helper, which is an instance of the Faker library.
// database/factories/ProductFactory.php
public function definition(): array
{
return [
'name' => fake()->words(3, true), // e.g., "Sleek Cotton Computer"
'description' => fake()->paragraph(),
'price' => fake()->randomFloat(2, 10, 500), // 2 decimal places, between 10 and 500
'stock_quantity' => fake()->numberBetween(0, 100),
];
}
Once defined, you can use tinker to quickly generate data.
30 Days to Learn Laravel, Ep 10 - Model Factories
Now, let's see factories in action. This next clip demonstrates how to use tinker to create records, how to customize a factory's definition, and how to generate multiple records at once.
Watch from 02:00 to 07:36. Note the syntax Model::factory()->count(50)->create() and how a new factory is generated and defined for a custom model.
The dev.to article, "Laravel Seeders & Factories," provides a handy cheat sheet of common Faker methods that you can refer to.
Laravel Seeders & Factories - Faker Cheat Sheet
For a quick reference of available Faker methods, check out this cheat sheet.
Scan the section '4. Faker Cheat Sheet — All Available Methods' to get a feel for the variety of data you can generate, from personal info to text and numbers.
Seeding Relationships
Our application's power comes from its relationships. A Product belongs to a Category, and an Order belongs to a User. Our fake data must reflect this.

The simplest way to handle a belongsTo relationship is to call the related model's factory directly within the definition.
// In ProductFactory.php
use App\Models\Category;
public function definition(): array
{
return [
'name' => fake()->words(3, true),
// ... other attributes
'category_id' => Category::factory(), // This will create a new Category for each Product!
];
}
This is just the beginning. Laravel offers a powerful, expressive API for seeding all types of relationships.
30 Days to Learn Laravel, Ep 10 - Model Factories
Let's watch a practical example of modifying a factory to account for a new relationship. This clip shows how to handle a belongsTo relationship by nesting a factory call.
Watch from 11:21 to 18:28. The key takeaway is seeing how adding an employer_id to the JobFactory works by calling Employer::factory(). This ensures that every fake job has a corresponding fake employer.
While nesting factories is great, Laravel also provides has() and for() methods which are often cleaner for defining one-to-many or many-to-many relationships from within a seeder.
How to Configure Database Seeding in Laravel - Seeding Relationships
For a comprehensive guide on seeding various relationship types, this article from OneUptime is an excellent resource. It provides clear code examples for the exact scenarios in our schema.
Read the section 'Seeding Relationships'. Focus on the 'One-to-Many' and 'Many-to-Many' examples, as they are directly applicable to our User/Order and Product/Category relationships.
3. Database Seeders: Executing the Plan
We now have the blueprints (factories). Next, we need to create the seeder scripts that will run them.
A seeder is a simple class with one method: run(). Your main entry point is database/seeders/DatabaseSeeder.php.
30 Days to Learn Laravel, Ep 15 - Understanding Database Seeders
This video explains the role of seeders and demonstrates the basic commands to run them, including the incredibly useful migrate:fresh --seed command.
Watch from the beginning to 02:54. Understand that seeders provide a repeatable way to populate the database after a migration, and that migrate:fresh --seed automates this entire process.
Organizing Your Seeders
While you can put all your logic in DatabaseSeeder.php, it's best practice to create separate seeder classes for each model. This keeps your code organized and allows you to seed parts of your database in isolation.
You can create a new seeder with make:seeder:
php artisan make:seeder ProductSeeder
Then, you call this seeder from your main DatabaseSeeder:
// In DatabaseSeeder.php
public function run(): void
{
$this->call([
CategorySeeder::class,
ProductSeeder::class,
UserSeeder::class,
OrderSeeder::class,
]);
}
Order is important! You must seed tables that are on the "one" side of a relationship before the "many" side (e.g., seed Categories before Products).
30 Days to Learn Laravel, Ep 15 - Understanding Database Seeders
Let's see how to create and use these separate seeder classes for better organization.
Watch from 03:59 to 05:53. The key concept is using php artisan make:seeder and then registering the new seeder class inside DatabaseSeeder's $this->call() method.
Your Turn: Let's Populate the Database
Now it's time to put all this theory into practice.
Step 1: Create the Factories
Generate factories for all your main models: Category, Product, User, and Order.
php artisan make:factory CategoryFactory --model=Category
php artisan make:factory ProductFactory --model=Product
# UserFactory already exists
php artisan make:factory OrderFactory --model=Order
Fill in the definition() method for each, using Faker to generate appropriate data. For the relationship fields:
ProductFactory: Set'category_id' => Category::factory().OrderFactory: Set'user_id' => User::factory().
Step 2: Create the Seeders
Generate a seeder for each model.
php artisan make:seeder CategorySeeder
php artisan make:seeder ProductSeeder
php artisan make:seeder UserSeeder
php artisan make:seeder OrderSeeder
Step 3: Implement the Seeder Logic
In each seeder's run() method, call the corresponding factory.
// In database/seeders/CategorySeeder.php
use App\Models\Category;
public function run(): void
{
// Create 10 categories
Category::factory()->count(10)->create();
}
// In database/seeders/ProductSeeder.php
use App\Models\Product;
use App\Models\Category;
public function run(): void
{
// Get all existing categories
$categories = Category::all();
// Create 50 products, recycling the existing categories
Product::factory()->count(50)->recycle($categories)->create();
}
- Challenge: Implement the
UserSeederandOrderSeeder. In theOrderSeeder, use therecycle()method (as shown in theProductSeederabove) to assign orders to the users you just created. - For the pivot tables (
category_product,order_product), you can use thehasAttachedfactory method after creating the primary models. For example, when creating products, you can attach them to random categories.
Step 4: Orchestrate in DatabaseSeeder
Open database/seeders/DatabaseSeeder.php and update the run() method to call your new seeders in the correct order.
// In DatabaseSeeder.php
public function run(): void
{
$this->call([
CategorySeeder::class,
UserSeeder::class,
ProductSeeder::class,
OrderSeeder::class,
]);
// Now, let's handle the many-to-many pivot tables.
$products = \App\Models\Product::all();
\App\Models\Category::all()->each(function ($category) use ($products) {
$category->products()->attach(
$products->random(rand(5, 15))->pluck('id')->toArray()
);
});
$orders = \App\Models\Order::all();
$orders->each(function ($order) use ($products) {
$order->products()->attach(
$products->random(rand(1, 5))->pluck('id')->toArray(),
['quantity' => rand(1, 3)] // Attaching pivot data
);
});
}
Step 5: Run It!
Execute the master command to drop all tables, re-run all migrations, and run all your seeders.
php artisan migrate:fresh --seed
After the command finishes, connect to your MSSQL database. You should see all your tables filled with thousands of rows of realistic-looking data!
Conclusion
Fantastic work! You have successfully built a complete, relational database schema and populated it with a rich set of test data. This is a massive step forward in our development process.
Key Takeaways:
- Factories are blueprints for creating single model instances with fake data via the
Fakerlibrary. - Seeders are executable scripts that call factories to populate the database.
- It's best practice to create separate seeder classes for each model and orchestrate them from
DatabaseSeeder.php. - Relationships are seeded by nesting factory calls or using powerful methods like
has(),for(), andrecycle(). - The
php artisan migrate:fresh --seedcommand is your most powerful tool for maintaining a clean, consistent, and populated development database.
Next Up:
Our database is no longer an empty shell; it's a living environment full of data. In the next module, "Advanced Eloquent ORM," we will start interacting with this data in sophisticated ways. We'll begin by learning how to apply accessors, mutators, and attribute casting to transform model data as it's being set or retrieved, making your models even more powerful and intelligent.