GSD

The Ultimate Guide to Laravel Migrations

Posted by Funke Olasupo on November 2, 2022

Database schemas can be modified from their existing state to the desired state via migrations. This can include adding tables and columns, removing items, or altering types and constraints.

For instance, if you're working on a team project and need to modify the application requirements, you might want to consider altering the database.

Usually, an updated .sql file is shared with each team member to import into their databases. However, this approach is neither ideal nor scalable because many things could go wrong and leave the application broken. For example, a team member can forget to import the file into their database, or even import an outdated version of the .sql file.

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 as well as the various benefits of Laravel migrations along with use cases. You will also learn various migration actions, commands, and events.

What are Laravel Migrations?

Laravel migrations enable developers to swiftly create, delete, and modify an application database without logging into the database manager or executing any SQL queries. 

It is vital to know that Laravel provides support for five databases. You are less likely to encounter support issues when using them. They include the following:

  • MariaDB 10.3+
  • MySQL 5.7+
  • PostgreSQL 10.0+
  • SQLite 3.8.8+
  • SQL Server 2017+

Why would you use Laravel Migrations?

Aside from version control, migrations make it easier to automate deployments and create temporary databases to run tests. They allow you to easily structure your models and tables and are an essential feature for seeders in your application.

Seeding is a simple method of generating dummy data for your application during testing.

Migrations also support developers by assisting with environment organization, validation, and secure implementation of schema modifications.

undefined

With migrations, you can alter the fields in your database without erasing the existing records. Laravel tracks the executed migrations within the database. As a result, the database can evolve as application requirements change.

How to create a migration

Each migration file defines a table in your database. It contains the schema to create or modify that table in the database. Laravel uses the make:migration Artisan command to create a database migration.

php artisan make:migration create_users_table

The command creates a migration file in your application's database/migrations directory. Laravel uses a timestamp prefix in the migration filename to determine the order of the migrations—for example, 2022_09_27_202310_create_users_table.php.

Laravel attempts to determine the table name and the migration action based on the filename. It also pre-fills the migration file with the specified table name from the attempted guess. You can manually define the table name in the migration file if the attempt fails.

You can specify a specific path within your application for the generated migration by using the --path argument with the make:migration command.

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

banner-cta-laravel-blue.webp

Structure of migrations

The generated migration file has a class definition with the methods up() and down() by default. The up() method is invoked every time you run a migration. However, the down() method is invoked whenever a database migration rolls back.

You can alter the database by adding new tables, columns, or indexes using the up() method, whereas the down() method carries out the up() method's operations in the reverse direction. 

For example, the up() method contains the schema to create a new table, add new columns to a table, or modify an existing column, while the down() method contains the schema to do the reverse, like drop a table, drop a column, etc.

Now, you can use the Schema Builder to create or modify tables in any of these methods.

Let's define a migration to create a table called users.

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUseTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('role');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('user');
    }
}

The Schema::create() shows that we are creating a table. The Schema::dropIfExists()  drops the specified table (which is ‘users’ in this case) if it exists on the database whenever a migration rolls back.

Running migrations

Before running your migrations, set up your database connection correctly. Update the .env file in the root directory of your application with the database configuration to match the following example, replacing the placeholders with details for your database.

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>

Clearing the cache is optional but may be necessary here. Run the following command in the terminal to do that:

php artisan config:cache

Use the migrate Artisan command to execute all of your pending migrations to the database:

php artisan migrate

Based on the migration defined earlier, you should see a users table in your database with five columns: id, name, role, created_at, and updated_at. The timestamps() in your migration file generate the created_at and updated_at columns for the database.

undefined

Tables, columns, and indexes

A database contains multiple tables. Both columns and rows are present in every table.

An index is a data structure that facilitates easy data retrieval operations on tables at the cost of additional writes and the storage space required to maintain the index data structure. Instead of searching through every row each time the database table is visited, you can use indexes to locate data quickly.

Tables

Creating a table

A table is a collection of data objects in a database organized in a table format which consists of rows and columns.

You can use the create method on the Schema facade to create a new database table. The create method takes two arguments: the table's name as the first argument and a closure that accepts a Blueprint object to define the new table as the second argument.

As you can see in the code snippet below, Schema::create accepts 'users', which is the table name, as our first argument. The second argument is a closure function where the Blueprint class with its $table object is passed as a parameter. This $table object is used to create/ modify a column for the users table.

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

$table->string('name'): This creates a string column called name in the users table.

$table->id(): This creates an auto-incrementing ID column which will be the primary key for this table.

$table->timestamps(): This creates updated_at and created_at TIMESTAMP columns. These columns will contain timestamps for when data is inserted and updated in the database, respectively.

These columns are not compulsory. You decide the columns to include based on your application’s needs.

Modifying your table

To rename tables, use the rename method on the Schema facade. Before renaming a table, confirm that all foreign key constraints on that table have an explicit name in your migration file instead of allowing Laravel to assign a convention-based name. Otherwise, the foreign key constraint name will refer to the old table.

In the code snippet below, we’ve renamed the users table to fund_users.

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

Schema::rename('users', 'fund_users');

You can use the drop / dropIfExists method on the Schema facade to drop tables. Dropping a table is the reverse of creating, so it can be in the down() method of the migration file. This way, the table is dropped whenever you roll back your migrations.  

Now in the code snippet below, let's drop the users table. You may take extra caution by using Schema::dropIfExists, so it will check if the table exists before dropping it, and if it doesn't exist, it continues execution to the next command without trying to drop.

If you use Schema::drop for a table that doesn't exist, it breaks execution and returns an error message.

Schema::drop('users');

//or
 
Schema::dropIfExists('users');

There are many other useful Schema methods that are in the official documentation

Here are some examples:

Schema::hasTable('users'): This checks if the database has a table called users.

Schema::hasTable('users','role'): This checks if the database has a users table with a column called role.

Columns

In a relational database, each database row has a corresponding column that contains a group of data values that are all of the same type. A table's columns define the structure of the data, whereas its rows populate the database with data.

Creating a column

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 any of these available fields. Now, in the code below I’ll add an extra column wallet to the users table:

Schema::table('users', function (Blueprint $table) {
    $table->double('wallet');
});

If you have migrated a table to your database and need to modify that table (say you're adding another column, for example), you should create a new migration file for the modifications. 

This is because the migration file has already been executed unless you prefer to roll back the migrations, make changes to the file, and migrate again. Rolling back a migration will mean dropping that table/column as well as the data it contains; therefore, it is not advisable if you want to retain the information in your database. 

Additionally, Laravel provides a list of column modifiers that allow a column to have additional properties. With this, you can give the wallet column a default value with the default() modifier :

Schema::table('users', function (Blueprint $table) {
    $table->double('wallet')->default(0);
});

Dropping a column

To drop columns, you can use the dropColumn() method on the Schema facade. In the down() method of this migration file, you can drop the column.

Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('wallet');
});

I wrote an article explaining adding and removing columns from existing tables in laravel migrations.

Modifying a column

To modify/rename a column, you must use the Composer package manager to install the doctrine/dbal package. To install it, run this command:

composer require doctrine/dbal

Additionally, you need to include the following configuration in your application's config/database.php file to use the timestamp() method:

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

You can now use the change() method to modify the type and attributes of existing columns.

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

You can use the renameColumn() method on the Schema facade to rename columns. Let's rename the name to full_name.

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

Indexes

Indexes are powerful tools used behind the scenes in databases to speed up queries. Indexes power queries by giving users a way to search for the data they need rapidly.

An index can be considered a pointer to a table's information. It's best to be careful when using indexes, especially when a table is updated frequently. The index is always regenerated with every update, slowing down the system.

An excellent example of an index type is unique. It requires all values in that column to be unique. This means the values can not be repeated in that column. In the following example, you will make an index for the email column in the users table to be unique. 

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

There are other available index types, including primary keys and foreign keys.

A foreign key in a relational database table is a column or set of columns connecting data from two different tables. It refers to the primary key of another table, creating a link between the two tables and serving as a cross-reference between them.

Now, introduce a books table to your database in a new migration file. The books table should have a foreign key to reference the user that owns the book. A good naming convention for foreign keys in Laravel is tablename_columnname. This is because Laravel attempts to guess the referenced table and column based on the name of the foreign key. 

You can also set your preferred foreign key, and use the references() and on() properties to point to the referenced column and table, respectively. This way, Laravel skips the attempted guess and uses the referenced table and column that were defined.

Use user_id in this example referencing the id column on the users table. In this method, you must define user_id as an unsignedBigInteger before defining its foreign key properties. 

Schema::table('books', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');
 
    $table->foreign('user_id')->references('id')->on('users');
});

Laravel also provides a shorter method that uses only the name of the foreign key to determine the name of the referenced table and the column. This is shorter because there will be no need to declare the unsignedBigInteger and include the references() and on() properties to the foreign key or also declare the unsignedBigInteger. You can also go ahead and include other foreign key properties like constrained(), etc.

Schema::create('books', function (Blueprint $table) {
        $table->foreignId('user_id')->constrained('users')
              ->onUpdate('cascade')->onDelete('cascade');
});

The constrained() method is optional and allows you to specify the table name if the conventions do not suit your table name.

The onUpdate() and onDelete() methods allow you to specify the action to take when the foreign key is deleted or updated. The cascade action means when rows in the parent table are deleted, the matching foreign key columns in the child table are also deleted, creating a cascading delete. Therefore, if you delete a user, their books are also deleted.

Any additional column modifiers must be called before constrained().

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

Removing migrations

There is no Artisan command to remove migrations. However, removing migrations depends on whether they have been executed in the database or not.

Use php artisan migrate:status to check if your migrations have been executed if you're not sure.

If you haven't run php artisan migrate to execute the migration, you can delete the migration file.

However, if the migration has been executed, you should follow these steps:

  1. It would be best to roll back that migration by running php artisan migrate:rollback. Remember that you don't want to roll back all the migrations, so include the batch number of the migration to be rolled back. It should look like php artisan migrate:rollback --step=1.
  2. Then, delete the migration file.

banner-cta-laravel-blue.webp

Squashing migrations

As your application develops over time, it may need more migrations. As a result, you may have acquired many migration files in your database/migrations directory. You can squash your migration files into a single SQL file using the schema:dump command.

php artisan schema:dump

When you run this command, Laravel will create a "schema" file in your application's database/schema directory. 

After that, if you try to migrate your database for the first time in your application, Laravel will run the SQL statements in the schema file first. Then it will run the remaining migrations that weren't included in the schema dump after running the lines in the schema file.

Migration events

Laravel provides some dispatch migration events that allow you to perform follow-up actions after a migration operation has been executed. For example, you can send an email to the admin every time a migration has finished executing. 

  • Illuminate\Database\Events\MigrationsStarted: This is used when a migration batch is about to be executed.
  • Illuminate\Database\Events\MigrationStarted: This is used when a single migration is about to be executed.
  • Illuminate\Database\Events\MigrationsEnded: This is used when a migration batch has ended execution.
  • Illuminate\Database\Events\MigrationEnded: This is used when a single migration has ended execution. 

These events extend the Illuminate\Database\Events\MigrationEvent class by default. Now, you can register a corresponding listener to any of these events in the app\Providers\EventServiceProvider directory.

<?php

use Illuminate\Database\Events\MigrationsEnded;

class EventServiceProvider extends ServiceProvider
{
  
    protected $listen = [
     	.
	.
	.
        MigrationsEnded::class=> [
	    // Corresponding Event Listener
        ],

   ];

You can read the official Laravel Events and Listeners documentation to understand events better.

Other migration commands

Laravel has some other Artisan commands that make migrations easy to use, including the following:

Rolling back migrations

You may want to revert the last batch of migrations to your database. Instead of deleting the columns and tables from the database manager, you can run this Artisan command:

php artisan migrate:rollback 

You can also roll back several migration batches using the -step option and batch number. Laravel tracks all migrations on the migrations table, including the batch number of migrations.

undefined

php artisan migrate:rollback --step=2

It rolls back the last two batches of migrations to your database. You can check the migrations table in your database if you are uncertain of the batch number of the migration to roll back.

Resetting migrations

You can use the migrate:reset command to roll back all your application migrations in the database.

php artisan migrate:reset

Refreshing migrations

You can use the migrate:refresh command to recreate your entire database. It rolls back all your migrations and executes the migrate command again.

php artisan migrate:refresh

That command is often mistaken for migrate:fresh. The migrate:fresh command drops all the tables in the database before executing the migrate command. 

php artisan migrate:fresh

Extra caution should be taken with migrate:fresh on applications that share databases because it drops all the tables in that database.

Creating a model with migration

When creating a model, you may use the —migration (or -m) option to create a corresponding database migration:

php artisan make:model Genre -m 

Using this command, Laravel creates a Genre.php model file in the app/Model directory and a xxxx_xx_xx_xxxxxx_create_genres_table migration file in database/migrations.

Closing 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 tutorial: How to Build a Blog with Laravel (& Send Slack Notifications).

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

Don’t miss a single post

Get our latest articles, stay updated!