How To Use Database Migrations and Seeders to Abstract Database Setup in Laravel

Migrations and seeders are powerful database utilities provided by the Laravel PHP framework to allow developers to quickly bootstrap, destroy and recreate an application’s database. These utilities help to minimize database inconsistency problems that can arise with multiple developers working on the same application: new contributors need only to run a couple artisan commands to set the database up on a fresh install.

In this guide, we’ll create migrations and seeders to populate a Laravel demo application’s database with sample data. At the end, you will be able to destroy and recreate your database tables as many times as you want, using only artisan commands.

Prerequisites

In order to follow this guide, you’ll need:

  • Access to an Ubuntu 18.04 local machine or development server as a non-root user with sudo privileges. If you’re using a remote server, it’s advisable to have an active firewall installed. To set these up, please refer to our Initial Server Setup Guide for Ubuntu 18.04.
  • Docker installed on your server, following Step 1 and Step 2 of How To Install and Use Docker on Ubuntu 18.04.
  • Docker Compose installed on your server, following Step 1 of How To Install Docker Compose on Ubuntu 18.04.

Note: In this guide, we’ll use a containerized development environment managed by Docker Compose to run the application, but you may also opt to run the application on a LEMP server. To set this up, you can follow our guide on How to Install and Configure Laravel with LEMP on Ubuntu 18.04.

Step 1 — Obtaining the Demo Application

To get started, we’ll fetch the demo Laravel application from its GitHub repository. We’re interested in the tutorial-02 branch, which includes a Docker Compose setup to run the application on containers. In this example, we’ll download the application to our home folder, but you can use any directory of your choice:

  • cd ~
  • curl -L https://github.com/do-community/travellist-laravel-demo/archive/tutorial-2.0.1.zip -o travellist.zip

Because we downloaded the application code as a .zip file, we’ll need the unzip command to unpack it. If you haven’t done so recently, update your machine’s local package index:

  • sudo apt update

Then install the unzip package:

  • sudo apt install unzip

Following that, unzip the contents of the application:

  • unzip travellist.zip

Then rename the unpacked directory to travellist-demo for easier access:

  • mv travellist-laravel-demo-tutorial-2.0.1 travellist-demo

In the next step, we’ll create a .env configuration file to set up the application.

Step 2 — Setting Up the Application’s .env File

In Laravel, a .env file is used to set up environment-dependent configurations, such as credentials and any information that might vary between deployments. This file is not included in revision control.

Warning: The environment configuration file contains sensitive information about your server, including database credentials and security keys. For that reason, you should never share this file publicly.

The values contained in the .env file will take precedence over the values set in regular configuration files located in the config directory. Each installation on a new environment requires a tailored environment file to define things such as database connection settings, debug options, and the application URL, among other items that may vary depending on which environment the application is running.

Navigate to the travellist-demo directory:

  • cd travellist-demo

We’ll now create a new .env file to customize the configuration options for the development environment we’re setting up. Laravel comes with an example.env file that we can copy to create our own:

  • cp .env.example .env

Open this file using nano or your text editor of choice:

  • nano .env

This is how your .env file looks like now:

.env

APP_NAME=Travellist APP_ENV=dev APP_KEY= APP_DEBUG=true APP_URL=http://localhost:8000   LOG_CHANNEL=stack  DB_CONNECTION=mysql DB_HOST=db DB_PORT=3306 DB_DATABASE=travellist DB_USERNAME=travellist_user DB_PASSWORD=password … 

The current .env file from the travellist demo application contains settings to use the containerized environment we’ve created with Docker Compose in the last part of this series. You don’t need to change any of these values, but you are free to modify the DB_DATABASE, DB_USERNAME and DB_PASSWORD if you wish, since these are pulled by our docker-compose.yml file automatically to set up the development database. Just make sure the DB_HOST variable remains unchanged, since it references the name of our database service within the Docker Compose environment.

If you make any changes to the file, make sure to save and close it by pressing CTRL + X, Y, then ENTER.

Note: If you have opted to run the application on a LEMP server, you’ll need to change the highlighted values to reflect your own database settings, including the DB_HOST variable.

Step 3 — Installing Application Dependencies with Composer

We’ll now use Composer, PHP’s dependency management tool, to install the application’s dependencies and make sure we’re able to execute artisan commands.

Bring up your Docker Compose environment with the following command.
This will build the travellist image for the app service and pull in the additional Docker images required by the nginx and db services, in order to create the application environment:

  • docker-compose up -d
OutputCreating network "travellist-demo_travellist" with driver "bridge" Building app Step 1/11 : FROM php:7.4-fpm  ---> fa37bd6db22a Step 2/11 : ARG user  ---> Running in 9259bb2ac034 … Creating travellist-app   ... done Creating travellist-nginx ... done Creating travellist-db    ... done 

This operation might take a few minutes to complete. Once the process is finished, we can run Composer to install the application’s dependencies.

To execute composer and other commands in the app service container, we’ll use docker-compose exec. The exec command allows us to execute any command of our choice on containers managed by Docker Compose. It uses the following syntax: docker-compose exec service_name command.

Note: In case you have opted to use a LEMP server to run the demo application, you should ignore the docker-compose exec app portion of the commands listed throughout this guide. For example, instead of running the following command as it’s written, you would just run:

  • composer install

To execute composer install in the app container, run:

  • docker-compose exec app composer install
OutputLoading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 85 installs, 0 updates, 0 removals   - Installing doctrine/inflector (1.3.1): Downloading (100%)            - Installing doctrine/lexer (1.2.0): Downloading (100%)            - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%)      … 

When Composer is finished installing the application’s dependencies, you’ll be able to execute artisan commands. To test that the application is able to connect to the database, run the following command which will clean up any pre-existing tables:

  • docker-compose exec app php artisan db:wipe

This command will drop any pre-existing tables on the configured database. If it ran successfully and the application is able to connect to the database, you’ll see output like this:

OutputDropped all tables successfully. 

Now that you have installed the application dependencies with Composer, you can use the artisan tool to create migrations and seeders.

Step 4 — Creating Database Migrations

The artisan command line tool that ships with Laravel contains a series of helper commands that can be used to manage the application and bootstrap new classes. To generate a new migration class, we can use the make:migration command as follows:

  • docker-compose exec app php artisan make:migration create_places_table

Laravel infers the operation to be executed (create), the name of the table (places), and whether this migration will create a new table or not, based on the descriptive name provided to the make:migration command.

You’ll see output similar to this:

OutputCreated Migration: 2020_02_03_143622_create_places_table 

This will generate a new file in the application’s database/migrations directory. The timestamp included in the auto-generated file is used by Laravel to determine in which order migrations should be executed.

Use your text editor of choice to open the generated migration file. Remember to replace the highlighted value with your own migration file name:

  • nano database/migrations/2020_02_03_143622_create_places_table.php

The generated migration file contains a class called CreatePlacesTable:

database/migrations/2020_02_03_143622_create_places_table.php

<?php  use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema;  class CreatePlacesTable extends Migration {     /**      * Run the migrations.      *      * @return void      */     public function up()     {         Schema::create('places', function (Blueprint $table) {             $table->bigIncrements('id');             $table->timestamps();         });     }      /**      * Reverse the migrations.      *      * @return void      */     public function down()     {         Schema::dropIfExists('places');     } }  

This class has two methods: up and down. Both methods contain bootstrap code that you can extend to customize what happens when that migration is executed and also what happens when it is rolled back.

We’ll modify the up method so that the places table reflects the structure we’re already using in the current application’s version:

  • id: primary key field.
  • name: name of the place.
  • visited: whether or not this place was already visited.

The Laravel schema builder exposes methods for creating, updating and deleting tables in a database. The Blueprint class defines the table’s structure and it provides several methods to abstract the definition of each table field.

The auto-generated code sets up a primary id field called id. The timestamps method creates two datetime fields that are automatically updated by the underlying database classes when data is inserted or updated within that table. In addition to these, we’ll need to include a name and a visited field.

Our name field will be of type string, and our visited field will be set with the boolean type. We’ll also set a default value of 0 for the visited field, so that if no value is passed, it means the place was not visited yet. This is how the up method will look like now:

database/migrations/2020_02_03_143622_create_places_table.php

…     public function up()     {         Schema::create('places', function (Blueprint $table) {             $table->bigIncrements('id');             $table->string('name', 100);             $table->boolean('visited')->default(0);             $table->timestamps();         });     } … 

Note: You can find the full list of available column types in the Laravel documentation.

After including the two highlighted lines on your own migration script, save and close the file.

Your migration is now ready to be executed via artisan migrate. However, that would only create an empty table; we also need to be able to insert sample data for development and testing. In the next step, we’ll see how to do that using database seeders.

Step 5 — Creating Database Seeders

A seeder is a special class used to generate and insert sample data (seeds) in a database. This is an important feature in development environments, since it allows you to recreate the application with a fresh database, using sample values that you’d otherwise have to manually insert each time the database is recreated.

We’ll now use the artisan command to generate a new seeder class for our places table called PlacesTableSeeder:

  • docker-compose exec app php artisan make:seeder PlacesTableSeeder

The command will create a new file called PlacesTableSeeder.php inside the database/seeds directory. Open that file using your text editor of choice:

  • nano database/seeds/PlacesTableSeeder.php

This is what the auto-generated PlacesTableSeeder.php file looks like:

database/seeds/PlacesTableSeeder.php

<?php  use IlluminateDatabaseSeeder;  class PlacesTableSeeder extends Seeder {     /**      * Run the database seeds.      *      * @return void      */     public function run()     {         //     } }  

Our new seeder class contains an empty method named run. This method will be called when the db:seed Artisan command is executed.

We need to edit the run method in order to include instructions to insert sample data in the database. We’ll use the Laravel query builder to streamline this process.

The Laravel query builder offers a fluent interface for database operations such as inserting, updating, deleting, and retrieving data. It also introduces safeguards against SQL injection attacks. The query builder is exposed by the DB facade – a static proxy to underlying database classes in the service container.

To get started, we’ll create a static class variable to hold all the sample places we want to insert into the database as an array. This will allow us to use a foreach loop to iterate through all values, inserting each one in the database using the query builder.

We’ll call this variable $places:

database/seeds/PlacesTableSeeder.php

<?php  use IlluminateDatabaseSeeder;  class PlacesTableSeeder extends Seeder {     static $places = [         'Berlin',         'Budapest',         'Cincinnati',         'Denver',         'Helsinki',         'Lisbon',         'Moscow',         'Nairobi',         'Oslo',         'Rio',         'Tokyo'     ]; … 

Next, we’ll need to include a use statement at the top of our PlacesTableSeeder class to facilitate referencing the DB facade throughout the code:

database/seeds/PlacesTableSeeder.php

<?php  use IlluminateDatabaseSeeder; use IlluminateSupportFacadesDB;  class PlacesTableSeeder extends Seeder … 

We can now iterate through the $places array values using a foreach loop, and insert each one in our places table using the query builder:

database/seeds/PlacesTableSeeder.php

…     public function run()     {         foreach (self::$places as $place) {             DB::table('places')->insert([                 'name' => $place,                 'visited' => rand(0,1) == 1             ]);         }     }  

The foreach loop iterates through each value of the $places static array. At each iteration, we use the DB facade to insert a new row at the places table. We set the name field to the name of the place we just obtained from the $places array, and we set the visited field to a random value of either 0 or 1.

This is what the full PlacesTableSeeder class will look like after all the updates:

database/seeds/PlacesTableSeeder.php

<?php  use IlluminateDatabaseSeeder; use IlluminateSupportFacadesDB;  class PlacesTableSeeder extends Seeder {     static $places = [         'Berlin',         'Budapest',         'Cincinnati',         'Denver',         'Helsinki',         'Lisbon',         'Moscow',         'Nairobi',         'Oslo',         'Rio',         'Tokyo'     ];      /**      * Run the database seeds.      *      * @return void      */     public function run()     {         foreach (self::$places as $place) {             DB::table('places')->insert([                 'name' => $place,                 'visited' => rand(0,1) == 1             ]);         }     } } 

Save and close the file when you’re done making these changes.

Seeder classes aren’t automatically loaded in the application. We need to edit the main DatabaseSeeder class to include a call to the seeder we’ve just created.

Open the database/seeds/DatabaseSeeder.php file using nano or your favorite editor:

  • nano database/seeds/DatabaseSeeder.php

The DatabaseSeeder class looks like any other seeder: it extends from the Seeder class and has a run method. We’ll update this method to include a call to PlacesTableSeeder.

Update the current run method inside your DatabaseSeeder class by deleting the commented-out line and replacing it with the following highlighted code:

database/seeds/DatabaseSeeder.php

…     public function run()     {         $this->call(PlacesTableSeeder::class);     } ... 

This is how the full DatabaseSeeder class will look like after the update:

database/seeds/DatabaseSeeder.php

<?php  use IlluminateDatabaseSeeder;  class DatabaseSeeder extends Seeder {     /**      * Seed the application's database.      *      * @return void      */     public function run()     {         $this->call(PlacesTableSeeder::class);     } }   

Save and close the file when you’re done updating its content.

We have now finished setting up both a migration and a seeder for our places table. In the next step, we’ll see how to execute them.

Step 6 — Running Database Migrations and Seeders

Before proceeding, we need to make sure your application is up and running. We’ll set up the application encryption key and then access the application from a browser to test the web server.

To generate the encryption key required by Laravel, you can use the artisan key:generate command:

  • docker-compose exec app php artisan key:generate

Once the key has been generated, you’ll be able to access the application by pointing your browser to your server hostname or IP address on port 8000:

http://server_host_or_ip:8000 

You’ll see a page like this:

MySQL error

That means the application is able to connect to the database, but it couldn’t find a table called places. We’ll create the places table now, using the following migrate artisan command:

  • docker-compose exec app php artisan migrate

You’ll get output similar to this:

OutputMigration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated:  2014_10_12_000000_create_users_table (0.06 seconds) Migrating: 2014_10_12_100000_create_password_resets_table Migrated:  2014_10_12_100000_create_password_resets_table (0.06 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated:  2019_08_19_000000_create_failed_jobs_table (0.03 seconds) Migrating: 2020_02_10_144134_create_places_table Migrated:  2020_02_10_144134_create_places_table (0.03 seconds) 

You’ll notice that a few other migrations were executed along with the create_places_table migration we’ve set up. These migrations are auto generated when Laravel is installed. Although we won’t be using these additional tables now, they will be needed in the future when we expand the application to have registered users and scheduled jobs. For now, you can just leave them as is.

At this point our table is still empty. We need to run the db:seed command to seed the database with our sample places:

  • docker-compose exec app php artisan db:seed

This will run our seeder and insert the sample values we defined within our PlacesTableSeeder class. You’ll see output similar to this:

OutputSeeding: PlacesTableSeeder Seeded:  PlacesTableSeeder (0.06 seconds) Database seeding completed successfully. 

Now, reload the application page on your browser. You’ll see a page similar to this:

Demo Laravel Application

Whenever you need to start from scratch, you can drop all your database tables with:

  • docker-compose exec app php artisan db:wipe
OutputDropped all tables successfully. 

To run the app migrations and seed the tables in a single command, you can use:

  • docker-compose exec app php artisan migrate --seed
OutputMigration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated:  2014_10_12_000000_create_users_table (0.06 seconds) Migrating: 2014_10_12_100000_create_password_resets_table Migrated:  2014_10_12_100000_create_password_resets_table (0.07 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated:  2019_08_19_000000_create_failed_jobs_table (0.03 seconds) Migrating: 2020_02_10_144134_create_places_table Migrated:  2020_02_10_144134_create_places_table (0.03 seconds) Seeding: PlacesTableSeeder Seeded:  PlacesTableSeeder (0.06 seconds) Database seeding completed successfully. 

If you want to roll back a migration, you can run:

  • docker-compose exec app php artisan migrate:rollback

This will trigger the down method for each migration class inside the migrations folder. Typically, it will remove all the tables that were created through migration classes, leaving alone any other tables that might have been manually created. You’ll see output like this:

OutputRolling back: 2020_02_10_144134_create_places_table Rolled back:  2020_02_10_144134_create_places_table (0.02 seconds) Rolling back: 2019_08_19_000000_create_failed_jobs_table Rolled back:  2019_08_19_000000_create_failed_jobs_table (0.02 seconds) Rolling back: 2014_10_12_100000_create_password_resets_table Rolled back:  2014_10_12_100000_create_password_resets_table (0.02 seconds) Rolling back: 2014_10_12_000000_create_users_table Rolled back:  2014_10_12_000000_create_users_table (0.02 seconds) 

The rollback command is especially useful when you’re making changes to application models and a db:wipe command can’t be used – for instance, if multiple systems depend on the same database.

Conclusion

In this guide, we’ve seen how to use database migrations and seeders to facilitate setting up development and testing databases for a Laravel 6 application.

As a next step, you might want to check the Laravel documentation for more details on how to use the query builder, and how to use Eloquent models to abstract your application’s database schema even further.