Introduction to Database Migrations in Laravel

Database migrations are one of the most powerful features of the Laravel framework. They provide a structured, version-controlled approach to managing database tables, columns, indexes, and relationships. Instead of manually creating tables in a database management tool or writing raw SQL, migrations allow you to define your database structure using PHP code. This makes your project consistent, portable, and maintainable.

In modern application development, the database layer is just as important as the application logic. Keeping track of changes, synchronizing schema updates among developers, and deploying updates across multiple environments can become challenging without a form of version control. Laravel migrations solve these problems by providing a clean, robust system for defining, modifying, and sharing database schema changes.

This comprehensive guide explains the concept of migrations, how to create them, how to run them, and how they fit into Laravel’s overall workflow. It expands the example you gave into a full-length explanation suitable for beginners and intermediate developers looking to master Laravel database migrations.

What Are Database Migrations

A database migration in Laravel is a PHP class that defines changes you want to make to your database. These changes may include creating new tables, adding columns, modifying existing structures, or establishing relationships between tables. Each migration acts as a version of the database structure.

For example, if you create a migration called create_products_table, it becomes part of a documented history of your database schema. Anyone working on the same project can run the migrations to synchronize their database structure with yours.

Migrations eliminate the need for manually writing SQL queries for table creation and editing. They turn database updates into an orderly and trackable process. This keeps your database schema clean, consistent, and easy to manage.


Why Migrations Are Essential in Modern Development

Modern web applications rely heavily on database consistency. Without a version-controlled system like migrations, developers often run into problems such as inconsistent tables, missing columns, mismatched data types, or database errors across environments.

Migrations solve these problems by providing:

A historical record of changes
A rollback system
A shareable database structure
A clean environment for new team members
A safe method to modify live databases

When working solo, migrations help you keep track of database changes between development phases. In large teams, migrations are indispensable for maintaining structure across environments like development, staging, and production.


The Role of Artisan in Managing Migrations

Laravel’s command-line tool, Artisan, plays an essential role in migrations. You use Artisan commands to create new migration files, run them, roll them back, and refresh or reset the database.

Your example command:

php artisan make:migration create_products_table

creates a migration file with a timestamp and descriptive name. Artisan immediately places it in the database/migrations directory. This timestamp ensures migrations run in the correct order, even when multiple migrations exist.

Artisan also provides commands like:

php artisan migrate
php artisan migrate:rollback
php artisan migrate:refresh
php artisan migrate:reset
php artisan migrate:fresh

These commands manage how migrations are executed, undone, or rebuilt.


Understanding the Structure of a Migration File

Every migration file contains two essential methods: up and down.

The up method defines the changes you want to apply to the database, such as creating a table or adding a column. The down method defines how to undo those changes, serving as a rollback mechanism.

A typical migration file looks like this:

public function up()
{
Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->integer('price');
    $table->timestamps();
});
} public function down() {
Schema::dropIfExists('products');
}

The up method runs when you execute php artisan migrate. The down method runs during a rollback, allowing you to revert changes safely.


Creating a Migration Using Artisan

To create a new migration, use the Artisan command:

php artisan make:migration create_products_table

The migration file will be generated inside:

database/migrations

The timestamp attached to the file name ensures that migrations run sequentially, reflecting the chronological order in which they were created.

The naming convention also hints at the migration’s purpose. In this example, create_products_table clearly signals an intention to create a new table named products.

Following descriptive naming conventions is essential, especially in large projects.


Defining Table Columns in a Migration

Once the migration file is created, the Schema builder allows you to define columns inside the table. Laravel supports a wide variety of column types such as:

string
text
integer
decimal
boolean
date
datetime
timestamp
json
uuid
foreignId

Your migration may look like this:

Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 8, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});

Laravel gives you expressive methods for defining table structures. The Blueprint object handles all column definitions and constraints.


Understanding Table Indexes and Keys

Migrations not only allow you to define columns but also indexes, primary keys, and foreign keys. For example:

$table->primary('id');
$table->unique('sku');
$table->index('category_id');
$table->foreign('category_id')->references('id')->on('categories');

Indexes improve query performance, unique ensures no duplicates, and foreign keys maintain relational integrity.

Laravel also offers foreignId shorthand:

$table->foreignId('category_id')->constrained();

This automatically creates a foreign key referencing categories.id.


Running Migrations with php artisan migrate

To apply all pending migrations, run:

php artisan migrate

Laravel will:

Check which migrations have not been executed
Run the up methods of those migrations
Record them in the migrations table

The migrations table lives in your database. Laravel uses this table to track which migration files have already been executed. This ensures migrations are never accidentally run twice.

If all migrations run successfully, Laravel updates the migrations table with a new entry for each migration file.


Rolling Back Migrations

Laravel provides rollback capabilities so you can undo the most recent batch of migrations:

php artisan migrate:rollback

This command triggers the down method of the most recently executed migration batch. A batch refers to all migrations run during a single migrate operation.

Rollback is very useful when:

Testing changes
Fixing mistakes
Undoing errors during development
Working with unstable database structures


Resetting and Refreshing the Database

In certain cases, you may want to rebuild the entire database. Laravel provides several commands for this.

Reset all migrations:

php artisan migrate:reset

This undoes all migrations by running all down methods.

To reset and run migrations again:

php artisan migrate:refresh

This is often used during development to rebuild the database cleanly.

To drop all tables and start fresh:

php artisan migrate:fresh

This deletes all tables and runs migrations without calling any rollback methods.


Modifying Existing Tables with Migrations

Migrations can modify existing tables as well. For example, to add a column:

php artisan make:migration add_stock_to_products_table

Inside the migration:

public function up()
{
Schema::table('products', function (Blueprint $table) {
    $table->integer('stock')->default(0);
});
} public function down() {
Schema::table('products', function (Blueprint $table) {
    $table->dropColumn('stock');
});
}

Using Schema::table allows you to update an existing table without recreating it.

You can also modify indexes, foreign keys, or column types.


Best Practices for Naming Migrations

Laravel developers follow naming conventions to maintain structure and clarity.

Popular naming patterns include:

create_products_table
add_price_to_products_table
add_details_to_users_table
remove_old_columns_from_orders_table
update_status_in_invoices_table

Using descriptive names makes migrations self-explanatory.


Version Control and Team Collaboration

Because migrations are plain code files, they integrate perfectly with version control systems like Git. When working in teams, you commit your migrations, push them to the repository, and other team members run them on their local machines.

This prevents the common issue where one developer adds a column but forgets to update the rest of the team. With migrations, every change is tracked and shareable.

Additionally, databases in development, staging, and production remain consistent because they all rely on the same migration history.


Migration Timestamps and Execution Order

Every migration file contains a timestamp as a prefix. This ensures that files run in the order they were created.

For example:

2025_06_14_154300_create_products_table.php
2025_06_14_155010_add_stock_to_products_table.php

Even if they were added minutes apart, the timestamp guarantees their intended sequence. This prevents dependency issues when one migration needs another table to exist first.


Handling Soft Deletes in Migrations

Laravel supports soft deletes with a simple column:

$table->softDeletes();

This adds a deleted_at timestamp column instead of actually deleting data. To undo a soft delete, you simply restore the record.

Soft deletes are commonly used in applications that require record recovery.


Adding Timestamps Automatically

By default, Laravel adds created_at and updated_at columns using:

$table->timestamps();

These timestamps are automatically managed by Eloquent models. They represent when a record was created or updated.


Dropping and Renaming Tables

To drop a table:

Schema::drop(‘products’);

Or to drop only if it exists:

Schema::dropIfExists(‘products’);

Renaming a table:

Schema::rename(‘old_name’, ‘new_name’);

Migrations allow you to restructure tables safely without manually altering the database.


Database Drivers and Compatibility

Laravel migrations support multiple database systems, including:

MySQL
PostgreSQL
SQLite
SQL Server

The Schema builder handles SQL differences behind the scenes. This means you write the same migration code regardless of database type.

This makes migration files portable and deployable across different environments.


Using Raw SQL in Migrations

In rare cases, you may need full control over raw SQL. Laravel provides DB::statement for this:

DB::statement(‘ALTER TABLE products MODIFY price DECIMAL(10,2)’);

Raw SQL should be used sparingly, but Laravel allows flexibility when necessary.


Seeding Data Alongside Migrations

Migrations work alongside seeders. Seeders populate the database with test or default data.

Example:

php artisan db:seed

or:

php artisan migrate –seed

This runs migrations and seeds the database in one command.


Combining Migrations with Factories

Factories generate sample data for testing. They complement migrations during development. After running migrations, you can populate a table using factories.

Example:

Product::factory()->count(50)->create();

This quickly fills your database with realistic test data.


Migrations in Deployment Workflows

Production deployments often involve running migrations automatically. When updating an application, new migrations are executed to reflect the latest database structure.

Migrations ensure:

Smooth deployments
No outdated schema
Reliable production databases


Common Errors and Troubleshooting Migration Issues

Common mistakes include:

Mismatched table names
Missing down methods
Foreign key constraints failing
Running migrations without refreshing caches

Troubleshooting involves checking logs, reviewing migration order, and verifying database configuration.


The Relationship Between Migrations and Eloquent Models

Eloquent models rely on database tables created through migrations. When building features, developers typically:

Create a migration
Define the model
Add relationships
Build controllers
Create views


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *