Database Design & Migrations
LaravelIn this lesson we'll create all the database migrations for our blog. Good database design is the foundation of any application.
Create All Migrations
Terminal
# Create models with migrations
php artisan make:model Category -m
php artisan make:model Tag -m
php artisan make:model Post -m
php artisan make:model Comment -m
# Create pivot table migration
php artisan make:migration create_post_tag_tableCategories Migration
database/migrations/create_categories_table.php
<?php
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->string('color', 7)->default('#6366f1'); // hex color
$table->timestamps();
});
}Tags Migration
database/migrations/create_tags_table.php
<?php
public function up(): void
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
}Posts Migration
database/migrations/create_posts_table.php
<?php
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
$table->foreignId('category_id')
->nullable()
->constrained()
->onDelete('set null');
$table->string('title');
$table->string('slug')->unique();
$table->text('excerpt')->nullable();
$table->longText('body');
$table->string('image')->nullable();
$table->boolean('published')->default(false);
$table->timestamp('published_at')->nullable();
$table->unsignedInteger('views')->default(0);
$table->timestamps();
$table->softDeletes(); // deleted_at column
// Index for faster queries
$table->index(['published', 'published_at']);
$table->index('slug');
});
}Post-Tag Pivot Migration
database/migrations/create_post_tag_table.php
<?php
public function up(): void
{
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id')
->constrained()
->onDelete('cascade');
$table->foreignId('tag_id')
->constrained()
->onDelete('cascade');
$table->primary(['post_id', 'tag_id']);
});
}Comments Migration
database/migrations/create_comments_table.php
<?php
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')
->constrained()
->onDelete('cascade');
$table->foreignId('user_id')
->nullable()
->constrained()
->onDelete('set null');
$table->string('author_name')->nullable(); // for guest comments
$table->string('author_email')->nullable();
$table->text('body');
$table->boolean('approved')->default(false);
$table->timestamps();
});
}Run Migrations
Terminal
php artisan migrate
# Check what was created
php artisan migrate:statusAdd Database Seeder
database/seeders/DatabaseSeeder.php
<?php
public function run(): void
{
// Create admin user
User::factory()->create([
'name' => 'Admin',
'email' => 'admin@blog.com',
]);
// Create categories
$categories = ['Laravel', 'PHP', 'JavaScript', 'DevOps', 'Tutorial'];
foreach ($categories as $cat) {
Category::create([
'name' => $cat,
'slug' => Str::slug($cat),
]);
}
// Create tags
$tags = ['beginner', 'advanced', 'tips', 'howto', 'guide'];
foreach ($tags as $tag) {
Tag::create(['name' => $tag, 'slug' => $tag]);
}
// Create sample posts
Post::factory(20)->create();
}
# Run seeder
# php artisan db:seed💡
Always add indexes! The
index() calls on frequently searched columns (slug, published) make queries much faster as your blog grows to thousands of posts.💡
SoftDeletes is important! With
softDeletes(), deleted posts aren't permanently removed — they just get a deleted_at timestamp. You can restore them later if needed.Stuck? Need help?
Review the previous lessons or check the Laravel documentation.