Database Migrations and Seeders
Database migrations provide a version control system for your database schema. They allow you to define and share the database structure with your team, making it e
Database migrations provide a version control system for your database schema. They allow you to define and share the database structure with your team, making it easy to modify the database schema over time while maintaining consistency across all environments.
Migrations are like version control for your database. They allow you to:
Basic migration file:
1<?php
2declare(strict_types=1);
3
4use Illuminate\Database\Migrations\Migration;
5use Illuminate\Database\Schema\Blueprint;
6use Illuminate\Support\Facades\Schema;
7
8return new class extends Migration
9{
10 /**
11 * Run the migrations.
12 */
13 public function up(): void
14 {
15 Schema::create('posts', function (Blueprint $table) {
16 $table->id();
17 $table->string('title');
18 $table->text('content');
19 $table->timestamps();
20 });
21 }
22
23 /**
24 * Reverse the migrations.
25 */
26 public function down(): void
27 {
28 Schema::dropIfExists('posts');
29 }
30};1# Create a new migration
2php artisan make:migration create_users_table
3
4# Create migration for specific table
5php artisan make:migration create_posts_table --create=posts
6
7# Create migration to modify table
8php artisan make:migration add_status_to_posts_table --table=posts
9
10# Create migration with path
11php artisan make:migration create_categories_table --path=database/migrations/blog1<?php
2declare(strict_types=1);
3
4use Illuminate\Database\Migrations\Migration;
5use Illuminate\Database\Schema\Blueprint;
6use Illuminate\Support\Facades\Schema;
7
8return new class extends Migration
9{
10 public function up(): void
11 {
12 Schema::create('users', function (Blueprint $table) {
13 // Primary key
14 $table->id(); // Alias for unsignedBigInteger with auto-increment
15
16 // String columns
17 $table->string('name');
18 $table->string('email')->unique();
19 $table->string('password');
20
21 // Nullable columns
22 $table->string('phone')->nullable();
23 $table->text('bio')->nullable();
24
25 // Integer columns
26 $table->unsignedInteger('age')->default(0);
27 $table->tinyInteger('status')->default(1);
28
29 // Boolean columns
30 $table->boolean('is_active')->default(true);
31 $table->boolean('email_verified')->default(false);
32
33 // Timestamps
34 $table->timestamps(); // created_at and updated_at
35 $table->timestamp('email_verified_at')->nullable();
36 $table->softDeletes(); // deleted_at for soft deletes
37 });
38 }
39
40 public function down(): void
41 {
42 Schema::dropIfExists('users');
43 }
44};1<?php
2declare(strict_types=1);
3
4Schema::create('products', function (Blueprint $table) {
5 // Numeric types
6 $table->id(); // BIGINT UNSIGNED AUTO_INCREMENT
7 $table->bigInteger('quantity');
8 $table->integer('stock');
9 $table->mediumInteger('views');
10 $table->smallInteger('rating');
11 $table->tinyInteger('priority');
12 $table->unsignedBigInteger('user_id');
13 $table->unsignedInteger('category_id');
14
15 // Decimal and float
16 $table->decimal('price', 8, 2); // Total 8 digits, 2 after decimal
17 $table->double('latitude', 10, 6);
18 $table->float('rating', 3, 1);
19
20 // String types
21 $table->string('name'); // VARCHAR(255)
22 $table->string('slug', 100); // VARCHAR(100)
23 $table->char('country_code', 2); // CHAR(2)
24 $table->text('description');
25 $table->mediumText('content');
26 $table->longText('body');
27
28 // Binary
29 $table->binary('data');
30
31 // Boolean
32 $table->boolean('is_featured');
33
34 // Date and time
35 $table->date('birth_date');
36 $table->dateTime('published_at');
37 $table->time('duration');
38 $table->timestamp('created_at');
39 $table->timestamps(); // created_at and updated_at
40 $table->timestampsTz(); // With timezone
41 $table->softDeletes(); // deleted_at
42 $table->year('year');
43
44 // JSON
45 $table->json('metadata');
46 $table->jsonb('settings'); // PostgreSQL
47
48 // UUID
49 $table->uuid('identifier');
50
51 // Enum
52 $table->enum('status', ['draft', 'published', 'archived']);
53 $table->set('permissions', ['read', 'write', 'delete']);
54
55 // Foreign key convenience method
56 $table->foreignId('user_id')->constrained();
57});1<?php
2declare(strict_types=1);
3
4Schema::create('posts', function (Blueprint $table) {
5 $table->id();
6
7 // Nullable
8 $table->string('title')->nullable();
9
10 // Default value
11 $table->string('status')->default('draft');
12 $table->integer('views')->default(0);
13 $table->boolean('is_published')->default(false);
14
15 // Unsigned
16 $table->integer('likes')->unsigned();
17
18 // Auto-increment
19 $table->integer('order')->autoIncrement();
20
21 // Comment
22 $table->string('slug')->comment('URL-friendly version of title');
23
24 // After specific column
25 $table->string('subtitle')->after('title');
26
27 // First column
28 $table->string('priority')->first();
29
30 // Invisible (MySQL 8.0.23+)
31 $table->string('internal_id')->invisible();
32
33 // Virtual generated column
34 $table->integer('price');
35 $table->integer('quantity');
36 $table->integer('total')->virtualAs('price * quantity');
37
38 // Stored generated column
39 $table->string('first_name');
40 $table->string('last_name');
41 $table->string('full_name')->storedAs("CONCAT(first_name, ' ', last_name)");
42
43 $table->timestamps();
44});1<?php
2declare(strict_types=1);
3
4Schema::create('posts', function (Blueprint $table) {
5 $table->id();
6 $table->string('title');
7 $table->string('slug');
8 $table->string('email');
9 $table->unsignedBigInteger('user_id');
10 $table->unsignedBigInteger('category_id');
11 $table->string('status');
12 $table->timestamps();
13
14 // Unique index
15 $table->unique('email');
16 $table->unique('slug', 'posts_slug_unique'); // Custom index name
17
18 // Regular index
19 $table->index('status');
20 $table->index('created_at');
21
22 // Composite index
23 $table->index(['user_id', 'status'], 'user_status_index');
24 $table->unique(['user_id', 'category_id'], 'user_category_unique');
25
26 // Full-text index (MySQL/PostgreSQL)
27 $table->fullText('title');
28 $table->fullText(['title', 'content']);
29
30 // Spatial index (MySQL)
31 // $table->spatialIndex('location');
32});
33
34// Add indexes after table creation
35Schema::table('posts', function (Blueprint $table) {
36 $table->index('user_id');
37 $table->unique('slug');
38});1<?php
2declare(strict_types=1);
3
4// Modern approach with foreignId
5Schema::create('posts', function (Blueprint $table) {
6 $table->id();
7 $table->string('title');
8
9 // Foreign key with automatic constraint
10 $table->foreignId('user_id')
11 ->constrained() // References 'id' on 'users' table
12 ->cascadeOnDelete(); // Delete posts when user is deleted
13
14 $table->foreignId('category_id')
15 ->constrained()
16 ->cascadeOnUpdate() // Update foreign key when parent updates
17 ->restrictOnDelete(); // Prevent deletion if posts exist
18
19 $table->timestamps();
20});
21
22// Explicit foreign key definition
23Schema::create('comments', function (Blueprint $table) {
24 $table->id();
25 $table->text('content');
26 $table->unsignedBigInteger('post_id');
27 $table->unsignedBigInteger('user_id');
28
29 // Define foreign keys explicitly
30 $table->foreign('post_id')
31 ->references('id')
32 ->on('posts')
33 ->onDelete('cascade');
34
35 $table->foreign('user_id')
36 ->references('id')
37 ->on('users')
38 ->onDelete('cascade');
39
40 $table->timestamps();
41});
42
43// Foreign key options
44Schema::create('orders', function (Blueprint $table) {
45 $table->id();
46
47 $table->foreignId('user_id')
48 ->constrained()
49 ->onDelete('cascade'); // Delete order when user deleted
50
51 $table->foreignId('product_id')
52 ->constrained()
53 ->onDelete('restrict'); // Prevent product deletion if orders exist
54
55 $table->foreignId('coupon_id')
56 ->nullable()
57 ->constrained()
58 ->nullOnDelete(); // Set to null when coupon deleted
59
60 $table->foreignId('shipping_address_id')
61 ->constrained('addresses') // Reference different table name
62 ->onDelete('set null');
63
64 $table->timestamps();
65});1<?php
2declare(strict_types=1);
3
4use Illuminate\Database\Migrations\Migration;
5use Illuminate\Database\Schema\Blueprint;
6use Illuminate\Support\Facades\Schema;
7
8return new class extends Migration
9{
10 public function up(): void
11 {
12 Schema::table('users', function (Blueprint $table) {
13 // Add new columns
14 $table->string('phone')->nullable()->after('email');
15 $table->text('bio')->nullable();
16 $table->boolean('is_verified')->default(false);
17 $table->timestamp('last_login_at')->nullable();
18
19 // Add index
20 $table->index('last_login_at');
21 });
22 }
23
24 public function down(): void
25 {
26 Schema::table('users', function (Blueprint $table) {
27 // Drop columns in reverse order
28 $table->dropIndex(['last_login_at']);
29 $table->dropColumn(['phone', 'bio', 'is_verified', 'last_login_at']);
30 });
31 }
32};1<?php
2declare(strict_types=1);
3
4use Illuminate\Database\Migrations\Migration;
5use Illuminate\Database\Schema\Blueprint;
6use Illuminate\Support\Facades\Schema;
7
8return new class extends Migration
9{
10 public function up(): void
11 {
12 Schema::table('posts', function (Blueprint $table) {
13 // Change column type
14 $table->text('content')->change();
15
16 // Make column nullable
17 $table->string('subtitle')->nullable()->change();
18
19 // Change default value
20 $table->string('status')->default('published')->change();
21
22 // Rename column
23 $table->renameColumn('old_name', 'new_name');
24
25 // Change multiple attributes
26 $table->string('title', 500)->nullable()->change();
27 });
28 }
29
30 public function down(): void
31 {
32 Schema::table('posts', function (Blueprint $table) {
33 $table->string('content')->change();
34 $table->string('subtitle')->nullable(false)->change();
35 $table->string('status')->default('draft')->change();
36 $table->renameColumn('new_name', 'old_name');
37 $table->string('title', 255)->nullable(false)->change();
38 });
39 }
40};1<?php
2declare(strict_types=1);
3
4Schema::table('posts', function (Blueprint $table) {
5 // Drop single column
6 $table->dropColumn('subtitle');
7
8 // Drop multiple columns
9 $table->dropColumn(['subtitle', 'summary']);
10
11 // Drop index
12 $table->dropIndex('posts_slug_index');
13 $table->dropUnique('posts_email_unique');
14 $table->dropFullText('posts_title_fulltext');
15
16 // Drop foreign key
17 $table->dropForeign('posts_user_id_foreign');
18 $table->dropForeign(['user_id']); // By column name
19
20 // Drop primary key
21 $table->dropPrimary('posts_id_primary');
22
23 // Drop timestamps
24 $table->dropTimestamps();
25 $table->dropSoftDeletes();
26});1# Run all pending migrations
2php artisan migrate
3
4# Run migrations with output
5php artisan migrate --verbose
6
7# Force migrations in production (bypass prompt)
8php artisan migrate --force
9
10# Run migrations in specific path
11php artisan migrate --path=database/migrations/2024_01_01_create_posts_table.php
12
13# Pretend to run migrations (show SQL)
14php artisan migrate --pretend
15
16# Run migrations and seeders
17php artisan migrate --seed
18
19# Show migration status
20php artisan migrate:status
21
22# Rollback last batch
23php artisan migrate:rollback
24
25# Rollback specific number of steps
26php artisan migrate:rollback --step=3
27
28# Rollback all migrations
29php artisan migrate:reset
30
31# Rollback and re-run all migrations
32php artisan migrate:refresh
33
34# Refresh and seed
35php artisan migrate:refresh --seed
36
37# Drop all tables and re-run migrations
38php artisan migrate:fresh
39
40# Fresh with seeding
41php artisan migrate:fresh --seed1# Create a seeder
2php artisan make:seeder UserSeeder
3
4# Create seeder for specific model
5php artisan make:seeder PostSeeder1<?php
2declare(strict_types=1);
3
4namespace Database\Seeders;
5
6use App\Models\User;
7use Illuminate\Database\Seeder;
8use Illuminate\Support\Facades\Hash;
9
10class UserSeeder extends Seeder
11{
12 /**
13 * Run the database seeds.
14 */
15 public function run(): void
16 {
17 // Create single user
18 User::create([
19 'name' => 'Admin User',
20 'email' => '[email protected]',
21 'password' => Hash::make('password'),
22 'role' => 'admin',
23 'is_active' => true,
24 ]);
25
26 // Create multiple users
27 $users = [
28 [
29 'name' => 'John Doe',
30 'email' => '[email protected]',
31 'password' => Hash::make('password'),
32 'role' => 'user',
33 ],
34 [
35 'name' => 'Jane Smith',
36 'email' => '[email protected]',
37 'password' => Hash::make('password'),
38 'role' => 'moderator',
39 ],
40 ];
41
42 foreach ($users as $userData) {
43 User::create($userData);
44 }
45
46 // Bulk insert (faster, but no events fired)
47 User::insert([
48 [
49 'name' => 'User 1',
50 'email' => '[email protected]',
51 'password' => Hash::make('password'),
52 'created_at' => now(),
53 'updated_at' => now(),
54 ],
55 [
56 'name' => 'User 2',
57 'email' => '[email protected]',
58 'password' => Hash::make('password'),
59 'created_at' => now(),
60 'updated_at' => now(),
61 ],
62 ]);
63 }
64}1<?php
2declare(strict_types=1);
3
4namespace Database\Seeders;
5
6use Illuminate\Database\Seeder;
7
8class DatabaseSeeder extends Seeder
9{
10 /**
11 * Seed the application's database.
12 */
13 public function run(): void
14 {
15 // Call seeders in order
16 $this->call([
17 UserSeeder::class,
18 CategorySeeder::class,
19 PostSeeder::class,
20 CommentSeeder::class,
21 ]);
22
23 // Conditional seeding
24 if (app()->environment('local')) {
25 $this->call([
26 TestDataSeeder::class,
27 ]);
28 }
29 }
30}1# Create a factory
2php artisan make:factory UserFactory
3
4# Create factory for specific model
5php artisan make:factory PostFactory --model=Post1<?php
2declare(strict_types=1);
3
4namespace Database\Factories;
5
6use App\Models\User;
7use Illuminate\Database\Eloquent\Factories\Factory;
8use Illuminate\Support\Facades\Hash;
9use Illuminate\Support\Str;
10
11/**
12 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
13 */
14class UserFactory extends Factory
15{
16 /**
17 * The current password being used by the factory.
18 */
19 protected static ?string $password = null;
20
21 /**
22 * Define the model's default state.
23 *
24 * @return array<string, mixed>
25 */
26 public function definition(): array
27 {
28 return [
29 'name' => fake()->name(),
30 'email' => fake()->unique()->safeEmail(),
31 'email_verified_at' => now(),
32 'password' => static::$password ??= Hash::make('password'),
33 'remember_token' => Str::random(10),
34 'bio' => fake()->paragraph(),
35 'phone' => fake()->phoneNumber(),
36 'is_active' => true,
37 ];
38 }
39
40 /**
41 * Indicate that the model's email address should be unverified.
42 */
43 public function unverified(): static
44 {
45 return $this->state(fn (array $attributes) => [
46 'email_verified_at' => null,
47 ]);
48 }
49
50 /**
51 * Indicate that the user should be inactive.
52 */
53 public function inactive(): static
54 {
55 return $this->state(fn (array $attributes) => [
56 'is_active' => false,
57 ]);
58 }
59
60 /**
61 * Indicate that the user should be an admin.
62 */
63 public function admin(): static
64 {
65 return $this->state(fn (array $attributes) => [
66 'role' => 'admin',
67 ]);
68 }
69}1<?php
2declare(strict_types=1);
3
4namespace Database\Factories;
5
6use App\Models\Category;
7use App\Models\Post;
8use App\Models\User;
9use Illuminate\Database\Eloquent\Factories\Factory;
10use Illuminate\Support\Str;
11
12/**
13 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
14 */
15class PostFactory extends Factory
16{
17 public function definition(): array
18 {
19 $title = fake()->sentence();
20
21 return [
22 'title' => $title,
23 'slug' => Str::slug($title),
24 'excerpt' => fake()->paragraph(),
25 'content' => fake()->paragraphs(5, true),
26 'status' => fake()->randomElement(['draft', 'published', 'archived']),
27 'published_at' => fake()->optional(0.7)->dateTimeBetween('-1 year', 'now'),
28 'views_count' => fake()->numberBetween(0, 10000),
29 'likes_count' => fake()->numberBetween(0, 1000),
30 'user_id' => User::factory(),
31 'category_id' => Category::factory(),
32 ];
33 }
34
35 /**
36 * Published post state.
37 */
38 public function published(): static
39 {
40 return $this->state(fn (array $attributes) => [
41 'status' => 'published',
42 'published_at' => fake()->dateTimeBetween('-6 months', 'now'),
43 ]);
44 }
45
46 /**
47 * Draft post state.
48 */
49 public function draft(): static
50 {
51 return $this->state(fn (array $attributes) => [
52 'status' => 'draft',
53 'published_at' => null,
54 ]);
55 }
56
57 /**
58 * Popular post state.
59 */
60 public function popular(): static
61 {
62 return $this->state(fn (array $attributes) => [
63 'views_count' => fake()->numberBetween(5000, 50000),
64 'likes_count' => fake()->numberBetween(500, 5000),
65 ]);
66 }
67}1<?php
2declare(strict_types=1);
3
4namespace Database\Seeders;
5
6use App\Models\Category;
7use App\Models\Comment;
8use App\Models\Post;
9use App\Models\Tag;
10use App\Models\User;
11use Illuminate\Database\Seeder;
12
13class DatabaseSeeder extends Seeder
14{
15 public function run(): void
16 {
17 // Create single user
18 $user = User::factory()->create();
19
20 // Create multiple users
21 User::factory()->count(50)->create();
22
23 // Create user with specific attributes
24 User::factory()->create([
25 'name' => 'Admin User',
26 'email' => '[email protected]',
27 'role' => 'admin',
28 ]);
29
30 // Use factory states
31 User::factory()->admin()->create();
32 User::factory()->unverified()->count(10)->create();
33 User::factory()->inactive()->count(5)->create();
34
35 // Create related models
36 User::factory()
37 ->count(10)
38 ->has(Post::factory()->count(5), 'posts')
39 ->create();
40
41 // Create with relationships using for()
42 Post::factory()
43 ->count(20)
44 ->for(User::factory(), 'author')
45 ->for(Category::factory())
46 ->create();
47
48 // Create posts with comments
49 Post::factory()
50 ->count(10)
51 ->has(
52 Comment::factory()
53 ->count(3)
54 ->for(User::factory())
55 )
56 ->create();
57
58 // Many-to-many relationships
59 $tags = Tag::factory()->count(10)->create();
60
61 Post::factory()
62 ->count(20)
63 ->hasAttached($tags)
64 ->create();
65
66 // Complex seeding
67 $admin = User::factory()->admin()->create();
68
69 $categories = Category::factory()->count(5)->create();
70
71 foreach ($categories as $category) {
72 Post::factory()
73 ->count(10)
74 ->published()
75 ->for($admin, 'author')
76 ->for($category)
77 ->has(Comment::factory()->count(5))
78 ->hasAttached($tags->random(3))
79 ->create();
80 }
81 }
82}1<?php
2declare(strict_types=1);
3
4// Good: Descriptive migration names
5// 2024_01_01_000001_create_users_table.php
6// 2024_01_01_000002_create_categories_table.php
7// 2024_01_01_000003_create_posts_table.php
8// 2024_01_15_100000_add_status_to_posts_table.php
9
10// Bad: Vague names
11// migration1.php
12// update_table.php1<?php
2declare(strict_types=1);
3
4namespace Database\Seeders;
5
6use Illuminate\Database\Seeder;
7use Illuminate\Support\Facades\DB;
8
9class DatabaseSeeder extends Seeder
10{
11 public function run(): void
12 {
13 DB::transaction(function () {
14 $this->call([
15 UserSeeder::class,
16 PostSeeder::class,
17 CommentSeeder::class,
18 ]);
19 });
20 }
21}1<?php
2declare(strict_types=1);
3
4// Create parent tables first
5Schema::create('users', function (Blueprint $table) {
6 $table->id();
7 $table->string('name');
8 $table->timestamps();
9});
10
11Schema::create('categories', function (Blueprint $table) {
12 $table->id();
13 $table->string('name');
14 $table->timestamps();
15});
16
17// Then create child tables with foreign keys
18Schema::create('posts', function (Blueprint $table) {
19 $table->id();
20 $table->string('title');
21 $table->foreignId('user_id')->constrained()->cascadeOnDelete();
22 $table->foreignId('category_id')->constrained()->restrictOnDelete();
23 $table->timestamps();
24});1<?php
2declare(strict_types=1);
3
4// Good: Properly reversible
5return new class extends Migration
6{
7 public function up(): void
8 {
9 Schema::create('posts', function (Blueprint $table) {
10 $table->id();
11 $table->string('title');
12 $table->timestamps();
13 });
14 }
15
16 public function down(): void
17 {
18 Schema::dropIfExists('posts');
19 }
20};
21
22// Bad: Cannot be rolled back
23return new class extends Migration
24{
25 public function up(): void
26 {
27 DB::statement('ALTER TABLE posts ADD COLUMN title VARCHAR(255)');
28 }
29
30 public function down(): void
31 {
32 // Missing rollback logic!
33 }
34};1<?php
2declare(strict_types=1);
3
4Schema::create('posts', function (Blueprint $table) {
5 $table->id();
6 $table->string('title');
7 $table->string('slug');
8 $table->foreignId('user_id');
9 $table->string('status');
10 $table->timestamp('published_at')->nullable();
11 $table->timestamps();
12
13 // Index foreign keys
14 $table->index('user_id');
15
16 // Index columns used in WHERE clauses
17 $table->index('status');
18 $table->index('published_at');
19
20 // Unique constraints
21 $table->unique('slug');
22
23 // Composite indexes for common queries
24 $table->index(['user_id', 'status']);
25 $table->index(['status', 'published_at']);
26});1<?php
2declare(strict_types=1);
3
4// 1. Create users table
5Schema::create('users', function (Blueprint $table) {
6 $table->id();
7 $table->string('name');
8 $table->string('email')->unique();
9 $table->string('password');
10 $table->string('role')->default('user');
11 $table->boolean('is_active')->default(true);
12 $table->timestamp('email_verified_at')->nullable();
13 $table->rememberToken();
14 $table->timestamps();
15 $table->softDeletes();
16
17 $table->index('email');
18 $table->index(['role', 'is_active']);
19});
20
21// 2. Create categories table
22Schema::create('categories', function (Blueprint $table) {
23 $table->id();
24 $table->string('name');
25 $table->string('slug')->unique();
26 $table->text('description')->nullable();
27 $table->unsignedBigInteger('parent_id')->nullable();
28 $table->integer('order')->default(0);
29 $table->timestamps();
30
31 $table->foreign('parent_id')
32 ->references('id')
33 ->on('categories')
34 ->nullOnDelete();
35
36 $table->index('slug');
37 $table->index('parent_id');
38});
39
40// 3. Create posts table
41Schema::create('posts', function (Blueprint $table) {
42 $table->id();
43 $table->string('title');
44 $table->string('slug')->unique();
45 $table->text('excerpt')->nullable();
46 $table->longText('content');
47 $table->string('featured_image')->nullable();
48 $table->enum('status', ['draft', 'published', 'archived'])->default('draft');
49 $table->timestamp('published_at')->nullable();
50 $table->unsignedInteger('views_count')->default(0);
51 $table->unsignedInteger('likes_count')->default(0);
52 $table->foreignId('user_id')->constrained()->cascadeOnDelete();
53 $table->foreignId('category_id')->constrained()->restrictOnDelete();
54 $table->timestamps();
55 $table->softDeletes();
56
57 $table->index('slug');
58 $table->index(['user_id', 'status']);
59 $table->index(['status', 'published_at']);
60 $table->fullText(['title', 'content']);
61});
62
63// 4. Create comments table
64Schema::create('comments', function (Blueprint $table) {
65 $table->id();
66 $table->text('content');
67 $table->unsignedInteger('likes_count')->default(0);
68 $table->foreignId('post_id')->constrained()->cascadeOnDelete();
69 $table->foreignId('user_id')->constrained()->cascadeOnDelete();
70 $table->unsignedBigInteger('parent_id')->nullable();
71 $table->timestamps();
72 $table->softDeletes();
73
74 $table->foreign('parent_id')
75 ->references('id')
76 ->on('comments')
77 ->cascadeOnDelete();
78
79 $table->index('post_id');
80 $table->index('user_id');
81 $table->index('parent_id');
82});
83
84// 5. Create tags table
85Schema::create('tags', function (Blueprint $table) {
86 $table->id();
87 $table->string('name');
88 $table->string('slug')->unique();
89 $table->timestamps();
90
91 $table->index('slug');
92});
93
94// 6. Create pivot table for posts and tags
95Schema::create('post_tag', function (Blueprint $table) {
96 $table->foreignId('post_id')->constrained()->cascadeOnDelete();
97 $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
98 $table->timestamps();
99
100 $table->primary(['post_id', 'tag_id']);
101});---
Database migrations and seeders provide:
Key Best Practices:
In the next lesson, we'll explore File Storage and Upload handling in Laravel.
Your progress is saved in this browser, on this device only.