Migrations

Managing database schema with Migrations

graph TD A[Create Migration] --> B[Migration File] B --> C[up Method] B --> D[down Method] C --> E[Run Migration] E --> F[Schema Changes Applied] F --> G[Rollback if Needed] G --> D H[Migrations Table] --> I[Track Executed Migrations]

Migrations allow you to manage database schema with version control. This system allows you to apply database schema changes incrementally and reversibly.


Benefits of Using Migrations:


  • <strong>Version Control</strong>: All schema changes are stored in migration files
  • <strong>Team Collaboration</strong>: Team members can easily sync schema
  • <strong>Environment Consistency</strong>: Same schema across all environments (development, staging, production)
  • <strong>Rollback Support</strong>: Ability to rollback changes if needed
  • <strong>History Tracking</strong>: Complete history of schema changes
  • <strong>Automation</strong>: Automatic migration execution in deployment

Migration Structure:


Each migration file contains two methods:


  • <code>up()</code>: Changes that should be applied (create table, add column, etc.)
  • <code>down()</code>: Changes that should be rolled back (drop table, remove column, etc.)

Naming Conventions:


  • Migration files are prefixed with timestamp
  • Migration name should be descriptive
  • Use snake_case for migration name

Migration Types:


  • <strong>Create Table</strong>: Create new table
  • <strong>Alter Table</strong>: Modify existing table
  • <strong>Drop Table</strong>: Drop table
  • <strong>Add Column</strong>: Add column
  • <strong>Modify Column</strong>: Modify column
  • <strong>Drop Column</strong>: Drop column
  • <strong>Create Index</strong>: Create index
  • <strong>Drop Index</strong>: Drop index

Best Practices:


  • Each migration should perform one specific change
  • Use transactions for migrations
  • Commit migration files
  • Use descriptive names
  • Always implement down() method

Examples

Creating Migration

php artisan make:migration create_users_table
php artisan make:migration add_email_to_users_table --table=users
php artisan make:migration drop_users_table --table=users

Artisan commands to create different types of migrations.

Migration File - Create Table

<?php

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

return new class extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
    
    public function down()
    {
        Schema::dropIfExists('users');
    }
};

A complete migration to create users table with all columns.

Migration - Add Column

<?php

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

return new class extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('phone')->nullable()->after('email');
            $table->boolean('is_active')->default(true);
            $table->index('email');
        });
    }
    
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['phone', 'is_active']);
            $table->dropIndex(['email']);
        });
    }
};

Adding new columns to existing table.

Migration - Foreign Keys

<?php

return new class extends Migration
{
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->foreignId('user_id')
                ->constrained()
                ->onDelete('cascade');
            
            $table->foreignId('category_id')
                ->nullable()
                ->constrained()
                ->onDelete('set null');
        });
    }
    
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropForeign(['user_id']);
            $table->dropForeign(['category_id']);
            $table->dropColumn(['user_id', 'category_id']);
        });
    }
};

Adding foreign keys with cascade and set null options.

Migration - Indexes

<?php

return new class extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->index('email');
            $table->unique('username');
            $table->index(['status', 'created_at']);
        });
    }
    
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropIndex(['email']);
            $table->dropUnique(['username']);
            $table->dropIndex(['status', 'created_at']);
        });
    }
};

Creating indexes to improve query performance.

Running Migrations

php artisan migrate
php artisan migrate:status
php artisan migrate:rollback
php artisan migrate:rollback --step=3
php artisan migrate:refresh
php artisan migrate:fresh
php artisan migrate:reset

Artisan commands to run and manage migrations.

Migration with Raw SQL

<?php

return new class extends Migration
{
    public function up()
    {
        DB::statement('ALTER TABLE users ADD FULLTEXT INDEX fulltext_name (name)');
    }
    
    public function down()
    {
        DB::statement('ALTER TABLE users DROP INDEX fulltext_name');
    }
};

Using raw SQL for complex operations not possible with Blueprint.

Use Cases

  • Managing schema changes in version control
  • Synchronizing database schema between environments
  • Rolling back schema changes if problems occur
  • Team collaboration for database changes
  • Automated deployment with schema updates
  • Tracking history of database changes

Common Mistakes

  • Forgetting to implement down() method
  • Creating migration with too many changes making rollback difficult
  • Not using transactions for migrations
  • Forgetting foreign keys in migrations
  • Not using indexes for performance
  • Modifying migration files that have already been run

Best Practices

  • Always implement down() method
  • Each migration should perform one specific change
  • Use descriptive names for migrations
  • Use transactions for migrations
  • Define foreign keys in migrations
  • Add indexes for frequently queried columns
  • Commit migration files and don't modify them

Edge Cases

  • Migrations with large datasets that may be slow
  • Migrations with data transformation
  • Migrations with multiple database connections
  • Migrations with custom database drivers
  • Rollback migrations with data loss
  • Migrations with conflicting changes

Performance Notes

  • Use indexes for frequently queried columns
  • Use batch operations for large data migrations
  • Use transactions for atomic operations
  • Use chunking for processing large datasets
  • Use raw queries for complex operations
  • Monitor migration execution time

Security Notes

  • Ensure migration files are in version control
  • Use raw SQL with caution
  • Ensure no sensitive data in migrations
  • Use environment variables for configuration
  • Ensure migrations are properly tested in production

Interview Points

  • What are migrations and why are they used?
  • What is the difference between migrate and migrate:fresh?
  • How can you rollback a migration?
  • Why should we implement down() method?
  • How can you add foreign key in migration?
  • What is the difference between migrate:refresh and migrate:reset?

Version Notes

  • Laravel 11.x: Improved performance in migration execution
  • Laravel 11.x: Better support for anonymous migrations
  • Laravel 10.x: Improved foreign key constraints
  • Laravel 9.x: Improved Blueprint API