GSD

How to Create, Run, & Manage Laravel Migrations

Posted by Funke Olasupo on January 26, 2024

Updated Jan 2024 by Deborah Emeni

Database schemas can be modified via migrations, such as adding or removing tables and altering types and constraints. For instance, when adjusting application requirements in a team project, you might want to consider altering the database. Typically, an updated .sql file is shared, but this approach is neither ideal nor scalable, as errors like forgetting to import or using an outdated version may occur.

Migrations act like version control for your database, allowing your team to share and define the schema of your application's database. Laravel migrations are essential because they help you tackle such database collaboration issues for a team. Your team members won't need to manually update their database whenever they pull your changes from source control.

In this tutorial, you will learn the importance of migrations in your Laravel application and the various benefits of Laravel migrations and use cases. You will also learn multiple migration actions, commands, and events by creating migrations for a sample Laravel project.

If you’re just getting started with Laravel, but still want to try out ButterCMS, check out our Laravel starter project to jumpstart your development.

What are Laravel Migrations?

Migrations streamline database schema updates, allowing development teams to implement, adjust, and manage changes collectively. They handle tasks like creation, deletion, and modification without manual SQL queries, supporting five databases, including MariaDB 10.10+, MySQL 5.7+, PostgreSQL 11.0+, SQLite 3.8.8+, and SQL Server 2017+.

In Laravel 10.x, several significant improvements have been made to the previous version, including argument and return types for application skeleton methods, updates to stub files, a developer-friendly process abstraction layer, Laravel Pennant for feature flags, and removal of unnecessary doc block type hints. This current version requires PHP 8.1 or later. Check the documentation for more details.

When to use Laravel migrations and why

You can leverage Laravel migrations whenever your development team needs to modify your application’s database schema. For example, if you want to create new tables, add columns, modify existing ones, or drop an entire table, Laravel migrations provide an easier way to collaborate with your team. It also offers version control for your schema and a secure method of managing database changes without having to write SQL queries directly.

undefined

Aside from version control, migrations simplify automation, aid in structuring models, and support developers with environment organization and secure schema modifications. Migrations in Laravel allow altering database fields without deleting existing records, enabling the database to evolve with changing application requirements.

Tutorial prerequisites

To follow along with this tutorial, ensure you have the following:

Note that the operating system used in this tutorial is MacOS, and if you use MacOS, you can install PHP and Composer in minutes via Laravel Herd.

Setting up the Laravel project

Let’s create a new Laravel project that you will use to create, use, and manage Laravel migrations. Using the Composer, run the following command:

composer create-project laravel/laravel voyage-app

Open the project in your code editor and start the local development server by running the following command:

php artisan serve

After running the command, your application will start on localhost:8000, opening in your browser as follows:

Laravel migrations local host

Setting up the database

Now that your application is running, you can set up your database. For this tutorial, use SQLite 3.8.8+ and create a new SQLite database in your project’s root directory by running the following command:

touch database/database.sqlite  

Once you run that command, a database.sqlite file will be created in your database directory.

How to create a migration

Laravel provides an Artisan command called make:migration that you can use to generate a migration file for your database, saving you the stress of doing it manually. Each migration file defines a table in your database containing the schema to create or modify the table in the database. 

In your project’s directory, run the following command to create a migration file:

php artisan make:migration create_users_table

Once you run the command, you’ll see the following in your terminal:

Laravel Migrations terminal results for create migrations

The command creates a migration file with a timestamp prefix in the database/migrations directory. Laravel attempts to determine the table name and migration action from the filename, pre-filling the migration file. If unsuccessful, you can manually define the table name. Use the --path argument with make:migration to specify a custom path in your application for the generated migration:

php artisan make:migration create_users_table --path=app/database/migrations

Laravel migration generators to help streamline your migration

Laravel’s built-in migration tools are powerful for use in your applications. However, relying solely on Laravel's built-in migration tools might become cumbersome when building complex projects. As a solution, you can streamline your migration process by utilizing third-party Laravel migration generators as follows:

  • This Laravel migration generator automatically analyzes your database and generates tables and columns along with their data types. It can examine your existing database to help reduce development time and minimize manual errors. Additionally, it includes indexes and generates foreign key constraints based on the existing relationships between your tables.

  • This Laravel migration generator is particularly useful for projects with numerous migrations that alter tables using ->change() from doctrine/dbal, which SQLite doesn’t support. It provides a solution for updating table structures in SQLite for use in tests. It is also useful for projects without databases and migrations, enabling the database's transformation into base migrations.

How to structure your Laravel migrations

The migration file includes an up() method for executing changes during migration (e.g., table creation) and a down() method for rolling back changes. For instance, up() might create a table, while down() removes it. The Schema Builder manages table creation/modification using these methods.

Let’s explore the structure of a migration file by defining one to create a users table.

The first step is to add the PHP start tag <?php at the top of your file, specifying that the code below will be written in PHP. Then, proceed with the following:

  • Import the Migration and Blueprint classes that define the structure of the migration.

  • Import the Schema facade, which provides access to the Schema builder.

<?php

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

Next, define an anonymous class that extends the Migration class with the following:

return new class extends Migration {}

Next, create a method that defines the logic for creating the database table as follows:

return new class extends Migration {

public function up(): void {}

}

Then, under the up() method, define the users table to include an auto-incrementing integer primary key column named id, along with two columns, created_at and updated_at, for timestamping purposes with the following:

return new class extends Migration {

public function up(): void {}
	Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
}

Next, add the down() method that defines the logic for reversing the migration with the following:

<?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('users', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });

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

Running your migration

Before running migrations, ensure your database connection is set up correctly. Update the .env file in your application's root directory with the database configuration, replacing the placeholders with your database details following the example below:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<Enter your database name>
DB_USERNAME=<Enter your database username>
DB_PASSWORD=<Enter your database password>

Next, run your migrations by using the Artisan migrate command to execute all pending migrations and update the database as follows:

php artisan migrate

After defining the migration, your database should have a users table with columns: id, created_at, and updated_at, generated by the timestamps() in your migration file:

Users table

Also, when deploying your application across multiple servers, ensure that your migrations run exclusively on one server at a time to avoid conflicts. Use the --isolated option when running your migrations as shown below:

php artisan migrate --isolated

When running migrations for your production database, you may encounter some that are destructive and could accidentally cause data loss. To avoid this, you'll be prompted to confirm your migration commands before execution. However, if you choose to skip this safety measure and execute them directly, you can use the --force flag when running your migration as follows:

php artisan migrate --force

How to create, modify, and drop tables

With Laravel Migrations, you can simplify and streamline your database schema management, making creating, modifying, and dropping tables easier. Rather than writing complex and error-prone SQL queries, you can leverage the methods provided by Laravel Migrations as discussed below:

Creating tables for Laravel migrations

A table is a collection of data objects in a database organized in a tabular format consisting of rows and columns. You can use the Schema facade's create() method to create a new database table by providing the table name and a closure defining the table using a Blueprint object.

In your migration file, replace the code in the up() function with the code to create a new users table. If the users table exists, run php artisan migrate:rollback before creating the new table:

Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('role');
            $table->timestamps();
});

In the code snippet above, the Schema::create('users', function($table) {...} defines and modifies columns for the users table, namely the id, name, string, and timestamps (created_at and updated_at). Run the migration with the command:

php artisan migrate

After running the command, you'll see in the terminal that your users table was created successfully:

Terminal running migrations

Check your database for the table with the specified columns:

Table with specified columns

Also, to check if a table or column exists and perform a specific action afterward, you can use the hasTable() and hasColumn() methods:

if (Schema::hasTable('users')) {
    // The "users" table exists...
}
 
if (Schema::hasColumn('users', 'role')) {
    // The "users" table exists and has an "email" column...
}

Modifying tables for Laravel migrations

Laravel migrations provide methods like rename in the Schema facade to modify tables. Ensure explicit foreign key constraint names in your migration file when renaming to avoid referencing the old table.

To rename a table, use the format $from for the current name and $to for the new name:

Schema::rename($from, $to);

Create another migration file called create_fund_users_table and use the rename() method to rename the users table to fund_users:

return new class extends Migration
{
    public function up(): void
    {
        Schema::rename('users', 'fund_users');
    }
}

Run the migration using the migrate command, and then check if the users table in your database has been renamed to fund_users:

Database renamed

How to drop tables

To reverse table creation, you can use the Schema facade's drop/dropIfExists method in a migration file's down() method. The snippet below drops the users table, and Schema::dropIfExists checks if the table exists before dropping it, continuing execution if it doesn't. To test, replace users with students in the migration file for the users table:

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

Then, run the following command to rollback the migration:

php artisan migrate:rollback

Despite the incorrect table name, running the rollback command will successfully roll back your migrations:

Successful rollback

Replace Schema::dropIfExists with Schema::drop to drop a non-existing table:

  public function down(): void
    {
        Schema::drop('students');
    }

When you run the migration rollback command, you'll encounter an error saying “no such table”:

No such table error

How to use columns within Laravel migrations

In a relational database, each row corresponds to a column with data values of the same type, defining the data structure. Laravel offers methods for column creation, deletion, and modification.

How to create columns

To update existing tables with additional columns, use the table() method in the Schema facade. It accepts the same arguments as the create() method. You can select the column's data type from these available fields

To add a wallet column to the fund_users table, create a migration file named add_wallet_column and include the following in the up() method of the generated file:

    public function up(): void
    {
        Schema::table('fund_users', function (Blueprint $table) {
            $table->double('wallet');
        });
    }

Run the migration and check your database for the new column:

Check database for new column

Important column types to remember

The Schema Builder Blueprint provides various methods for column types in your database tables. Here are some examples:

Column Types

Description

ENUM

It's created with the enum() method and comprises predefined values.

BOOLEAN

It's created with the boolean() method and can store true or false values.

BLOB

It utilizes the binary() method to store data such as images, audio, or files.

DATETIME

It uses the dateTime() method to store both date and time, with an optional $precision argument (default 0) for fractional seconds.

VARCHAR

Created with the string() method, it stores variable-length text data.

UNSIGNED INTEGER

Created with the increments() method, it stores an auto-incrementing integer primary key.

INTEGER

Created with the integer() method to store whole numbers.

DATE

Created with the date() method, it stores date-only information.

TIMESTAMP

Created with the timestamp() method to store record creation and last update timestamps.

TIME

It utilizes the time() method and stores extensive text.

Important column modifiers

Laravel migrations offer column modifiers to enhance data storage, allowing null entries, defining column order and default values, and generating values based on other columns. Let’s discuss some column modifiers in the table below:

Column Modifier

Description

->default($value)

This sets the default value for a column during data insertion.

->after('column')

This specifies the position of a new column in an existing table.

->nullable($value = true)

This allows the specified column to store NULL values.

->useCurrentOnUpdate()

This updates the timestamp column to the current time when a table record is updated.

->storedAs($expression)

This defines stored generated columns.

->from($integer)

This sets the initial value for an auto-incrementing column.

->autoIncrement()

This automatically increments a column by one when inserting a new record.

->comment('my comment')

This adds descriptive notes as comments to your database schema.

->generatedAs($expression)

This generates a virtual column value using a specified expression.

Laravel migrations allow setting initial values with Default Expressions during table creation, which can be simple values or complex functions. You can use these expressions to assign default values to JSON columns. Additionally, Laravel migrations provide control over Column Order, allowing precise positioning of new columns within tables for organized schema and aligned data.

How to drop a column

You can use the dropColumn() method on the Schema facade to drop columns. Let’s try dropping the wallet column that we added. To do this, you need to create another migration file. In your migration file, under the up() method, drop the wallet column with the following code:

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

Run the migration and check if the wallet column is removed from your database:

Check if wallet column is removed

How to modify a column

To modify a column in SQLite, install the doctrine/dbal package using Composer by running the following command in your project directory:

composer require doctrine/dbal

After installation, modify your column using the timestamp() method. Include the following configuration in your app's config/database.php file:

use Illuminate\Database\DBAL\TimestampType;
 
'dbal' => [
    'types' => [
        'timestamp' => TimestampType::class,
    ],
],

Create a new migration file and add the following to use the change() method to modify the role column data type to string with a maximum length of 50 characters. Also, use the nullable() method to make the column nullable, meaning it can accept NULL values:

Schema::table('fund_users', function (Blueprint $table) {
    $table->string('role', 50)->nullable()->change();
});

Run the migrate command and verify that the role column in your database is updated to a maximum length of 50 characters:

Update role column

Use the Schema facade's renameColumn() method to rename the name column to full_name by adding the changes to a new migration file:

Schema::table('fund_users', function (Blueprint $table) {
    $table->renameColumn('name', 'full_name');
});

Run the migrate command and verify the renaming of the name column to full_name in your database:

Verify the renaming of the column

How to use indexes within Laravel migrations

Indexes enhance database query speed by quickly directing users to the needed data, acting as pointers to table information. However, it's best to be careful when using indexes, as frequent table updates lead to index regeneration, causing system slowdowns.

How to create indexes

An excellent example of an index type is unique, ensuring all values in a column are distinct. Create a migration file to make the email address column unique by adding the following:

Schema::table('fund_users', function (Blueprint $table) {
    $table->string('email address')->unique();
});

Run your migration file and the email address column will be defined as unique in your database as follows:

email address column as unique

Important index types

Other available index types are supported by Laravel. Let’s explore the index types below:

Index Types

Description

Primary Key

$table->primary('id'): Creates a unique primary key on the specified column.

Unique Index

$table->unique('email'): Creates a unique index on the specified column to prevent duplicate values.

Non-Unique Index

$table->index('name'): Creates non-unique index for faster searches and allows duplicate values.

Fulltext Index

$table->fullText('description'): Creates a full-text index for optimized keyword search within text data.

Foreign Key

$table->foreign('user_id')->references('id')->on('users'): This creates a foreign key on 'user_id', linking to 'id' in the 'users' table for data integrity and relationships.

If using older MySQL or MariaDB versions (before 5.7.7 and 10.2.2, respectively), manually configure the default string length in migrations by calling Schema::defaultStringLength in AppServiceProvider's boot method. You can use the following to set the default length for string columns created in future migrations to 200 characters:

use Illuminate\Support\Facades\Schema;
 
public function boot(): void
{
    Schema::defaultStringLength(200);
}

How to drop indexes

Laravel automatically assigns an index name based on the table's name, indexed column, and index type. To drop an index, specify the assigned name in the migration's up() method:

public function up(): void
    {
        Schema::table('fund_users', function (Blueprint $table) {
            $table->dropUnique('fund_users_email_address_unique');
        });
    }

The $table->dropUnique('fund_users_email_address_unique'); removes the unique index 'fund_users_email_address_unique' on the email address column, which includes the table name, column name, and key type.

Run your migrations and confirm the removal of the unique constraint by inserting a duplicate email_address without encountering errors.

How to rename indexes

The Schema Builder Blueprint provides a renameIndex method requiring the current and new index names as arguments. Create a new migration file and add a unique() index for a home address column: 

public function up(): void
    {
        Schema::table('fund_users', function (Blueprint $table) {
            $table->string('home address')->unique();
        });
    }

Verify the new indexed column in your database:

Verify home address column
Rename the index by creating a new migration file. Add the following code to rename your existing index on the fund_users table from fund_users_home_address_unique to office_address:

public function down(): void
    {
        Schema::table('fund_users', function (Blueprint $table) {
            $table->renameIndex('fund_users_home_address_unique', 'office_address');
        });
    }

How to handle foreign key constraints

Foreign key constraints in relational databases, like those in Laravel using the foreign() and references() methods, maintain data accuracy by ensuring values in a “foreign key” column exist in the corresponding “primary key” column of the referenced table. 

To see this in action, create a new migration file for a books table called create_books_table and run the migrations:

Create new migrations

Next, create a migration file called create_foreign_key_constraints_for_books_table to define foreign key constraints referencing the fund_users table. Follow Laravel's naming convention (tablename_columnname) and use the foreign() method with references() and on() properties to specify the referenced column and table (fund_user_id referencing id in the fund_users table):

public function up(): void
    {
        Schema::table('books', function (Blueprint $table) {
            $table->unsignedBigInteger('fund_user_id');
         
            $table->foreign('fund_user_id')->references('id')->on('fund_users');
        });
    }

Run the migration to create the fund_user_id in your database:

Create fund user id

In addition, Laravel provides a concise foreignId() method, eliminating the need for declaring unsignedBigInteger, references(), on(), or additional unsignedBigInteger for the foreign key. You can also include other properties like constrained() as follows:

Schema::create('books', function (Blueprint $table) {
        $table->foreignId('fund_user_id')->constrained(table: 'fund_users', indexName: 'books_user_id')
              ->onUpdate('cascade')->onDelete('cascade');
});

The constrained() method is optional for specifying a custom table name. Use onUpdate() and onDelete() to define actions for foreign key updates or deletions, with cascade deleting rows in the child table when the parent table's rows are deleted. Any additional column modifiers must precede constrained().

For example, the constrained() method defines a foreign key relationship in the following code snippet. It specifies that the fund_user_id column in the books table references the primary key of the fund_users table:

Schema::create('books', function (Blueprint $table) {
       $table->foreignId('fund_user_id')
             ->nullable()
             ->constrained('fund_users');
   });

To remove a foreign key constraint, you can use Laravel’s dropForeign() method by specifying the constraint name, which is a combination of the table name, the column in the constraint, and a "_foreign" suffix in a migration file. To drop the foreign key constraint, create a migration file and use the following:

 public function up(): void
    {
        Schema::table('books', function (Blueprint $table) {
            $table->dropForeign('fund_user_id_foreign');
        });
    }

To toggle (enable or disable) foreign key constraints in migrations, use these methods enableForeignKeyConstraints(), disableForeignKeyConstraints(), withoutForeignKeyConstraints():

Schema::enableForeignKeyConstraints();
 
Schema::disableForeignKeyConstraints();
 
Schema::withoutForeignKeyConstraints(function () {
    // Constraints disabled within this closure...
});

How to remove migrations

While there's no Artisan command specifically for removing migrations, you can achieve this by rolling back executed migrations, thereby reversing their impact on the database schema. To check if migrations have been executed, use the command:

php artisan migrate:status

After running the command, you'll see the following in your terminal:

status command terminal output

If you haven't executed php artisan migrate, you can just delete the migration file by right-clicking and selecting delete:

Select delete

However, if the migration has been executed, you can roll back a specific migration using php artisan migrate:rollback with the batch number. To remove the last migration, use the command:

php artisan migrate:rollback --step=1

The -step option specifies the migrations to rollback; for example, -step=2 rolls back the last two migrations. After executing the command, the last migration is rolled back:

Last migration rolled back in terminal

Then, delete the migration file. If using SQLite, clear the database.sqlite file before rerunning the migrate command to avoid errors. Alternatively, drop all tables to remove all migrations at once.

How to squash migrations

As your application scales, it may require additional migrations, leading to numerous files in the database/migrations directory. You can use the schema:dump command to squash these files into a single SQL file by running this command:

php artisan schema:dump

When you run this command, Laravel generates a "schema" file in your app's database/schema directory:

generated schema file

The schema file contains SQL statements. When migrating your database for the first time in Laravel, it runs the SQL statements in the schema file first and then proceeds with the remaining migrations not included in the schema dump.

How to use migration events

Laravel provides dispatch migration events for post-migration actions, such as notifying the admin via email. Events include the following:

  • MigrationsStarted - when a batch is about to execute.

  • MigrationStarted - when a single migration is about to execute.

  • MigrationsEnded - when a batch execution has ended.

  • MigrationEnded - when a single migration execution has ended.

These events extend the Illuminate\Database\Events\MigrationEvent class by default. You can register a listener (a class that responds to specific events within your application) in the app\Providers\EventServiceProvider directory for these events. Register the MigrationsStarted event by importing it in your EventServiceProvider.php file:

use Illuminate\Database\Events\MigrationsStarted;

Create a listener class to handle the event by running the following command:

php artisan make:listener MigrationStartedListener

This command creates a Listeners/MigrationStartedListener directory and class and handle() function for handling migration events. Add the following code under the handle() function to echo "Butters migration has started" before executing a migration batch:

 public function handle(object $event): void
    {
        echo "Butters migration has started";
    }

Next, go back to EventServiceProvider.php and import this class:

use App\Listeners\MigrationStartedListener;

To map the listener to the event, include the following in your protected $listen array:

MigrationsStarted::class => [
            MigrationStartedListener::class
]

Run your migration to see the echoed message in your terminal:

undefined

Final thoughts

You've come a long way, congrats!

In this article, you have learned how to use migrations to improve your Laravel applications. The source code for migration files, including the highlighted examples, is available on GitHub. You can also find more information about Laravel migrations in the official documentation. Now you can see that migrations are necessary to monitor database changes, which aid in error detection and rollbacks in case of an error.

If you'd like to see the power of Laravel combined with ButterCMS check our tutorials: How to Build a Blog with Laravel (& Send Slack Notifications)How to Build Compelling Laravel Landing Pages with ButterCMS

Make sure you receive the freshest Butter product updates and tutorials directly in your inbox.
Funke Olasupo

Funke is a tech lover with a keen interest in building and promoting sustainable tech communities, especially among women and young adults. She is a backend developer, who is passionate about communal growth and has published articles on many blogs including her personal blog.

ButterCMS is the #1 rated Headless CMS

G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award G2 crowd review award

Don’t miss a single post

Get our latest articles, stay updated!