Laravel

Laravel is a powerful and popular PHP framework that simplifies and accelerates web application development. Its elegant syntax, robust features like Eloquent ORM, Blade templating, and built-in security tools help developers create efficient and scalable apps. With strong community support and extensive documentation, Laravel is an ideal choice for both novice and experienced developers

Laravel Migrations: A Comprehensive Guide

19 May 2025 | Category:

Laravel migrations provide a powerful, database-agnostic way to define and manage your database schema using PHP code, enabling version control for database changes. Migrations allow you to create, modify, and drop tables or columns, ensuring consistent database structures across development, testing, and production environments. This SEO-friendly, plagiarism-free guide explains how to create, run, and manage migrations in Laravel, with practical examples and best practices. Based on Laravel 11 (as of May 19, 2025), this tutorial is designed for beginners and intermediate developers and complements the previous discussion on database configuration.


What are Laravel Migrations?

Migrations are PHP classes that describe database schema changes, such as creating tables, adding columns, or defining indexes. They serve as a version-control system for your database, allowing you to:

  • Define schema in code.
  • Apply changes incrementally.
  • Roll back changes if needed.
  • Share schema changes with your team via version control.

Each migration file contains two methods:

  • up(): Applies the schema change (e.g., create a table).
  • down(): Reverts the change (e.g., drop the table).

Migrations are stored in the database/migrations/ directory and are executed in chronological order based on their timestamped filenames.

Key Benefits

  • Version Control: Track and share database changes via Git.
  • Consistency: Ensure identical schemas across environments.
  • Reversibility: Roll back changes with minimal effort.
  • Database Agnostic: Write migrations once for MySQL, PostgreSQL, SQLite, etc.
  • Team Collaboration: Simplify schema updates in team projects.

Setting Up Migrations

Before creating migrations, ensure your database is configured in the .env file (as covered previously). For example:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret

Verify the connection:

php artisan tinker
>>> DB::connection()->getPdo();

Creating a Migration

Laravel’s Artisan CLI simplifies migration creation. Use the make:migration command to generate a migration file.

Basic Migration

php artisan make:migration create_posts_table

This creates a file in database/migrations/ (e.g., 2025_05_19_123456_create_posts_table.php):

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
  • up(): Defines the schema (creates the posts table with an id and timestamps).
  • down(): Reverts the change (drops the posts table).
  • Blueprint: A fluent interface for defining table structure.
  • Schema: Facade for schema operations.

Naming Conventions

  • Create Table: create_table_name (e.g., create_posts_table).
  • Modify Table: add_column_to_table_name or modify_table_name (e.g., add_slug_to_posts_table).
  • Descriptive Names: Use clear names to indicate the purpose (e.g., create_users_table, add_status_to_orders_table).

Defining a Migration

You can customize the up() and down() methods to define your schema. Below are common column types and modifiers.

Common Column Types

$table->id(); // Auto-incrementing primary key (BIGINT)
$table->string('name', 255); // VARCHAR(255)
$table->text('description'); // TEXT
$table->integer('quantity'); // INTEGER
$table->boolean('is_active')->default(true); // BOOLEAN
$table->decimal('price', 8, 2); // DECIMAL(8,2) for currency
$table->date('published_at')->nullable(); // DATE
$table->timestamp('created_at')->nullable(); // TIMESTAMP
$table->timestamps(); // created_at and updated_at
$table->foreignId('user_id')->constrained()->onDelete('cascade'); // Foreign key
  • Length: Specify for string (e.g., string('name', 100)).
  • Precision: For decimal (e.g., 8,2 for 123456.78).

Common Modifiers

  • nullable(): Allows NULL values.
  • default($value): Sets a default value.
  • unsigned(): For non-negative integers.
  • unique(): Adds a unique constraint.
  • index(): Creates an index for faster queries.
  • after(‘column’): Places the column after another (MySQL only).

Example: Full Migration

public function up(): void
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title', 255);
        $table->text('content');
        $table->boolean('is_published')->default(false);
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->string('slug')->unique()->after('title');
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('posts');
}
  • Creates a posts table with:
  • Auto-incrementing id.
  • title (VARCHAR 255).
  • content (TEXT).
  • is_published (BOOLEAN, default false).
  • user_id (foreign key referencing users, cascades on delete).
  • slug (unique VARCHAR after title).
  • created_at and updated_at (via timestamps()).

Running Migrations

Apply migrations to the database using Artisan commands. Laravel tracks applied migrations in the migrations table.

Apply Migrations

php artisan migrate
  • Runs all unapplied migrations in database/migrations/.
  • Creates the migrations table if it doesn’t exist.

Rollback Migrations

Revert the last batch of migrations:

php artisan migrate:rollback
  • Executes the down() method for the most recent batch.

Other Commands

  • Reset: Revert all migrations:
  php artisan migrate:reset
  • Refresh: Roll back and re-run all migrations:
  php artisan migrate:refresh
  • Fresh: Drop all tables and re-run migrations:
  php artisan migrate:fresh
  • Status: Check which migrations have run:
  php artisan migrate:status

Seeding with Migrations

Combine migrations with seeders for initial data:

php artisan migrate:fresh --seed

Modifying Existing Tables

To modify a table (e.g., add, rename, or drop columns), create a new migration.

Example: Add a Column

php artisan make:migration add_status_to_posts_table

Migration:

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('status')->default('draft')->after('is_published');
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('status');
    });
}
  • Schema::table: Modifies an existing table (vs. Schema::create).
  • after(‘is_published’): Positions the column.

Example: Rename a Column

Install doctrine/dbal for column renaming:

composer require doctrine/dbal

Migration:

php artisan make:migration rename_status_to_state_in_posts_table
public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->renameColumn('status', 'state');
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->renameColumn('state', 'status');
    });
}

Example: Drop a Column

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('slug');
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('slug')->unique()->after('title');
    });
}

Foreign Keys and Relationships

Foreign keys enforce referential integrity. Use foreignId() for simplicity.

Example:

$table->foreignId('user_id')->constrained()->onDelete('cascade');
  • constrained(): Links to the id column of the users table.
  • onDelete(‘cascade’): Deletes posts if the referenced user is deleted.
  • Alternatives: onDelete('set null'), onDelete('restrict').

Complex Foreign Key:

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
  • More explicit syntax for custom foreign keys.

Seeding and Factories (Optional Integration)

Migrations often pair with seeders and factories to populate the database.

Create a Seeder

php artisan make:seeder PostSeeder

Seeder (database/seeders/PostSeeder.php):

public function run(): void
{
    Post::create([
        'title' => 'Sample Post',
        'content' => 'This is a sample post.',
        'is_published' => true,
        'user_id' => 1,
    ]);
}

Create a Factory

php artisan make:factory PostFactory

Factory (database/factories/PostFactory.php):

public function definition(): array
{
    return [
        'title' => fake()->sentence,
        'content' => fake()->paragraph,
        'is_published' => fake()->boolean,
        'user_id' => User::factory(),
    ];
}

Use in Seeder:

public function run(): void
{
    Post::factory()->count(10)->create();
}

Run:

php artisan db:seed

Or with migrations:

php artisan migrate:fresh --seed

Best Practices for Migrations

  1. Use Descriptive Names:
  • E.g., create_posts_table, add_slug_to_posts_table.
  1. Keep Migrations Atomic:
  • One migration per table or change for easier rollbacks.
  1. Define down() Properly:
  • Ensure down() reverses up() accurately.
  1. Use Foreign Keys:
  • Enforce relationships with foreignId()->constrained().
  1. Test Migrations:
  • Run migrations in a local/test environment first.
  • Use migrate:status to verify.
  1. Backup Production Databases:
  • Back up before running migrations in production.
  1. Avoid Data Modifications:
  • Use seeders for data, not migrations.
  1. Leverage Factories:
  • Use factories for consistent test data.
  1. Document Complex Migrations:
  • Add comments or maintain schema documentation.
  1. Clear Cache:
    • Run php artisan config:clear if .env changes affect migrations.

Debugging Migrations

  • Syntax Errors:
  • Check migration files for typos or invalid column types.
  • Connection Issues:
  • Verify .env settings and test with DB::connection()->getPdo().
  • Migration Conflicts:
  • Ensure timestamps are unique to avoid execution order issues.
  • Use migrate:status to identify unapplied migrations.
  • Table Exists:
  • Use Schema::dropIfExists() in down() to avoid errors.
  • Logs:
  • Check storage/logs/laravel.log for detailed errors.
  • Verbose Output:
  • Run php artisan migrate --verbose for more details.

Example: Complete Migration Workflow

Step 1: Create a Migration

php artisan make:migration create_posts_table

Migration:

public function up(): void
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title', 255);
        $table->text('content');
        $table->boolean('is_published')->default(false);
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->string('slug')->unique()->after('title');
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('posts');
}

Step 2: Create a Model and Factory

php artisan make:model Post -mf
  • -m: Skips migration (already created).
  • -f: Creates a factory.

Model (app/Models/Post.php):

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['title', 'content', 'is_published', 'user_id', 'slug'];
}

Factory (database/factories/PostFactory.php):

public function definition(): array
{
    return [
        'title' => fake()->sentence,
        'content' => fake()->paragraph,
        'is_published' => fake()->boolean,
        'user_id' => User::factory(),
        'slug' => fake()->unique()->slug,
    ];
}

Step 3: Run Migrations

php artisan migrate

Step 4: Seed Data

Seeder (database/seeders/PostSeeder.php):

public function run(): void
{
    Post::factory()->count(10)->create();
}

Run:

php artisan migrate:fresh --seed

Conclusion

Laravel migrations are a cornerstone of database management, offering a structured, version-controlled approach to schema changes. By creating migrations with Artisan, defining tables and columns with the schema builder, and running them with commands like migrate and rollback, you can maintain consistent and reversible database structures. Integrating migrations with models, factories, and seeders streamlines development and testing workflows.

Next Steps:

  • Create a migration: php artisan make:migration create_table_name.
  • Define a table schema with columns and foreign keys.
  • Run migrations and seed data: php artisan migrate:fresh --seed.

For deeper insights, explore Laravel’s official documentation or engage with the Laravel community on platforms like X. Start managing your database schema with migrations today!

Laravel Migrations Example

This artifact provides a practical example of creating and running a migration for a posts table, including a model, factory, and seeder.

Migration (database/migrations/2025_05_19_123456_create_posts_table.php)

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title', 255);
            $table->text('content');
            $table->boolean('is_published')->default(false);
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('slug')->unique()->after('title');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Model (app/Models/Post.php)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['title', 'content', 'is_published', 'user_id', 'slug'];
}

Factory (database/factories/PostFactory.php)

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title' => fake()->sentence,
            'content' => fake()->paragraph,
            'is_published' => fake()->boolean,
            'user_id' => User::factory(),
            'slug' => fake()->unique()->slug,
        ];
    }
}

Seeder (database/seeders/PostSeeder.php)

<?php

namespace Database\Seeders;

use App\Models\Post;
use Illuminate\Database\Seeder;

class PostSeeder extends Seeder
{
    public function run(): void
    {
        Post::factory()->count(10)->create();
    }
}

Usage

  1. Ensure your .env file is configured (e.g., MySQL, PostgreSQL, or SQLite).
  2. Save the migration, model, factory, and seeder files.
  3. Run migrations:
   php artisan migrate
  1. Seed the database:
   php artisan db:seed

Or combine:

   php artisan migrate:fresh --seed
  1. Verify the posts table and data in your database.

This example demonstrates a migration for a posts table with a foreign key to users, a model with fillable attributes, a factory for generating fake data, and a seeder to populate the table.