How to add a column using Laravel Migration

There are many mistakes I made along the way of learning Laravel. Here I put together this post to mark one of the most important lessons I just learned. What is the right way to use Laravel Migration to add a new column without dropping a table! This article is not trying to tell you which line of code doing what job. It’s more about which step is the right way to go in a certain development context. It’s hard to grasp the full picture of the development cycle from Laravel document because it always lists all the options and tell you the details for each option, but doesn’t explain what should do next and why do it in this way.

Let’s start with a particular case here. A book list app, simple. It lists some books from the database. What I want to do is add a new column to an existing books table through Laravel Migration.

Create the books table

First, create books table migration

php artisan make:migration create_books_table

After this command line, you will get a file under “`/database/migrations“` folder. Something like this:

2019_03_05_175510_create_books_table.php

Edit this file to add 3 columns: title, author, content.

<?php

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

class CreateBooksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasTable('books')) {
return;
}
Schema::create('books', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
$table->string('title');
$table->string('author');
$table->text('content');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('books');
}
}

If you run php artisan migrate  you will get a new books table in your database.

Now here is the question, at this stage, how can I add a new column to your books table?

When you google this question, the most common answer you get is that run php artisan migrate:refresh. Please!!! DON’t DO IT lightly. It will drop all your tables and data and re-create everything from the start. Unless you knew this is what you want.

Adding a new column in the wrong way

This is what I did.

I went back to that 2019_03_05_175510_create_books_table.php  file and added a new line there. Like this:

Schema::create('books', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
            $table->string('title');
            $table->string('author');
            $table->text('content'); 
            $table->string('genres');   // <--- add this line
        });

Then I try to run php artisan migrate again, hope it finds the change in this file. Then I got:

Nothing to migrate.

The correct way to add a new column

After some googling and researching. I finally found the right way to do this. It turns out that the timestamp prefix in migration file hint that you should never change it after you run migration on that file. It’s more like one time job. Leave it as it is and create a new migration.

php artisan make:migration add_genres_books_table

Edit the new file 2019_03_05_192355_add_genres_books_table.php

<?php

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

class AddGenresBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        if (Schema::hasTable('books')) {
            if (!Schema::hasColumn('books','genres')) {
                Schema::table('books', function (Blueprint $table) {
                    $table->string('genres');
                });
            }
        }
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        if (Schema::hasTable('books')) {
            if (Schema::hasColumn('books','genres')) {
                Schema::table('books', function (Blueprint $table) {
                    $table->dropColumn('genres');
                });
            }
        }
    }
}

Then run php artisan migrate again, I got:

Migrating: 2019_03_05_192355_add_genres_books_table
Migrated:  2019_03_05_192355_add_genres_books_table

The new column is added in books table. Great!

Now, If I run php artisan migrate:rollback,  I get:

Rolling back: 2019_03_05_192355_add_tag_books_table
Rolled back:  2019_03_05_192355_add_tag_books_table

The new column is dropped from books table.

Conclusion

Now, Everything starts making sense to me. The Laravel Migration plays an important role here which is a version controller. Once a migration job is done, you shouldn’t change it. It was time-stamped and stored in migration history php artisan migrate:status.

If you want to modify your database in Laravel, you should create a new migration and implement the up and down functions in pair. They must do exactly the opposite works. If you only finish up function, then your rollback job will fail!