Create a Blog with Laravel Migrations, Model Factories and Seeding

Create a Blog with Laravel Migrations, Model Factories and Seeding

Published: 1/9/20219 min read
Laravel
PHP
MySQL

When I was first introduced to Laravel, less then 2 years ago, I was immediately taken by it. The rich echo system, the fast-paced updates, a huge community of developers and the relative ease with which you can get a project up and running. However, there were some DB features like Migrations and Seeding I never fully explored. When I started incorporating migrations and seeding into my project, my development process become more productive and efficient.
In this tutorial, we will create a simple blog with migrations, model factories and DB seeding. There is a lot to cover here and I cannot dive deeply into every subject. Laravel is a feature rich framework – and very well documented! So, for each subject I’ll be writing about, there is a lot more to cover (seriously, a lot!). I’ve included links to relevant sections of Laravel’s official docs in every part of this tutorial. This tutorial uses Laravel 8.x.

We will:
  • Setup a Laravel project
  • Connect to a DB
  • Create models
  • Create migrations
  • Create factories
  • Seed DB

Here is the GitHub repository for this tutorial:
👉 https://github.com/yossi-abramov/laravel-blog-migration-factory-seed

Setup a Laravel project

First, let’s create a fresh Laravel project. Make sure you have composer installed, then you can start a new Laravel project with the Laravel Installer or with composer create-project. If you want to use the Laravel installer follow the Laravel official docs here: 👉 https://laravel.com/docs/8.x/installation
For this tutorial, I will be installing Laravel with composer create-project.
First, check if you have composer installed:

composer -v
# or 
composer --version

If you don’t have composer installed, you should head over to https://getcomposer.org and get composer.
Now, let’s create a Laravel project:

composer create-project laravel/laravel laravel-blog-migration-factory-seed

Connect to a DB

For this tutorial I’ll be using MySQL via Shell. First, login to your DB of choice (via Shell, GUI – pick your tool), Then, create a database by running:

CREATE DATABASE blog_example;

Now, you need to tell Laravel about your DB connection, host, port, name, username and password. Go to .env in your project’s root directory and change the following block:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog_example
DB_USERNAME=your_db_user_name
DB_PASSWORD=your_db_password

Now, before continuing, test your DB connection. There are a few ways to do this. My favorite is by using Laravel Tinker which is an awesome REPL (read–eval–print loop) tool that's included by default in Laravel applications.
Open the terminal, then cd to your project's root folder, or run in your IDE’s terminal:

php artisan tinker // starts the REPL
DB::connection()->getPdo() // get a PDO connection object

If the REPL outputs a PDO object, you’re all set, and your DB is connected to Laravel!
Exit the REPL by running exit.

Models

For this project we will have 4 Models: User, Post, Tag and PostTag. Laravel comes with a solid User model, migration, factory and seeder so we can skip it for now. Let’s create a Post model, migration and factory with one php artisan command:

php artisan make:model Post -mf

By running the command we’ve created 3 files:

/app/Models/Post.php
/database/factories/PostFactory.php 
/database/migrations/xxxx_xx_xx_xxxxxx_create_posts_table.php

I want to focus on migrations and DB seeding with model factories, so we will not go over models in this tutorial. Let’s run these artisan commands to generate the necessary files for Tag and PostTags:

php artisan make:model Tag -mf
php artisan make:model PostTag -m

Read more about Laravel models in the official Docs:
👉 https://laravel.com/docs/8.x/eloquent

Migrations

DB migrations allows you to create and manage DB schemas. With migrations, you do not have to use a CREATE TABLE statement and can dive into creating tables, defining relationships, modifying columns and much more. Now, let’s setup our Post model’s first migration.
Go to /database/migrations/xxxx_xx_xx_xxxxxx_create_posts_table.php. This is the migration artisan generated for us:

<?php

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

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

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

As you can see, we have two methods.The up() method will run when we execute migrations with the php artisan migrate command. The down() method will run when we rollback our migrations with the php artisan migrate:rollback command. Let’s go over the up() method – this is where you define your scheme and preform most of the migration logic.
To create, modify and remove tables and/or columns, Laravel offers us the Schema façade. Then, $table is an instance of the Blueprint class (dependency injection). $table is packed with methods to help you mange tables! Here is our Post migration logic:

<?php

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

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id(); 
            
            $table->bigInteger('user_id') 
            ->foreign('user_id')
            ->references('id')
            ->on('users');
            
            // VARCHAR equivalent column with a length.
            $table->string('slug', 255)
            ->unique(); // Index
            
            $table->string('title', 255);  
            
            // TEXT equivalent column.
            $table->text('description'); 
            $table->text('post');
            $table->timestamps();
        });
    }

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

For a list of available methods see:
👉 https://laravel.com/docs/8.x/migrations#columns
Here is our Tag migration:

<?php

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

class CreateTagsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('tag', 255);
            $table->timestamps();
        });
    }
    
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('tags');
    }
}

And the PostTag migration:

<?php

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

class CreatePostTagsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('post_tags', function (Blueprint $table) {
            $table->id();

            $table->bigInteger('post_id') 
            ->foreign('post_id')
            ->references('id')
            ->on('posts');
            
            $table->bigInteger('tag_id') 
            ->foreign('tag_id')
            ->references('id')
            ->on('tags');
            
            $table->timestamps();
        });
    }
    
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('post_tags');
    }
}

Now, let’s run our migrations with:

php artisan migrate

Model Factories

Model factories are an excellent way to create test data for your applications. If you need fake data to mockup an application or fake data for database testing, model factories are an excellent solution.
Here is the Post model factory generated by the artisan command we ran:

<?php

namespace Database\Factories;

use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Post::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            //
        ];
    }
}

In the definition() method we will define a “state” for the factory. Laravel’s Factory class comes with the Faker library baked in. Php Faker is a powerful tool for generating fake data. Here is the Faker GitHub repo for all available methods: 👉 https://github.com/fzaninotto/Faker
There are many ways of creating a factory for our posts. Here is my quick and dirty suggestion:

<?php

namespace Database\Factories;

use App\Models\Post;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Post::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        $title = $this->faker->sentence;
        
        $post = collect($this->faker->paragraphs(rand(5, 15)))
        ->map(function($item){
            return "<p>$item</p>";
        })->toArray();

        $post = implode($post);

        return [
            'user_id' => 1,
            'title' => $title,
            'description' => $this->faker->paragraph,
            'slug' => Str::slug($title),
            'post' => $post,
        ];
    }
}

We can check our factory with tinker. Start tinker with:

php artisan tinker

Run factory with:

Post::factory()->count(1)->create()

tinker will output the generated post/s in the console and will insert the data to your db. You can check your DB with tinker by running:

Post::all()

Here is our Tag factory:

<?php

namespace Database\Factories;

use App\Models\Tag;
use Illuminate\Database\Eloquent\Factories\Factory;

class TagFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Tag::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */

    public function definition()
    {
        return [
            'tag' => $this->faker->word
        ];
    }
}

Test it with tinker:

php artisan tinker
Tag::factory()->count(1)->create()

We will not be creating a PostTag Factory.
Read more about model factories in the official docs:
👉 https://laravel.com/docs/8.x/database-testing

Seeders

DB seeders allow us to insert data to our DB with ease. For DB seeding we can call Laravel’s DB façade or use model factories. The default DB seeder is located at: /database/seeds/DatabaseSeeder.php:

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        // \App\Models\User::factory(10)->create();
    }

}

In the run() method, we can call other seeders that call model factories, or just call our factories directly. We will call model factories and use the DB façade:

<?php

namespace Database\Seeders;

use App\Models\Tag;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        User::factory(1)->create(); // Create a single user
        Post::factory(50)->create(); // Create 50 posts
        Tag::factory(8)->create(); // Create 8 tags

        foreach(Post::all() as $post){ // loop through all posts 
            $random_tags = Tag::all()->random(rand(2, 5))->pluck('id')->toArray();
            // Insert random post tag
            foreach ($random_tags as $tag) {
                DB::table('post_tags')->insert([
                    'post_id' => $post->id,
                    'tag_id' => Tag::all()->random(1)[0]->id
                ]);
            }
        }
    }
}

To seed your DB run:

php artisan db:seed

Now your DB is full of posts 😎!
Check posts with tinker:

php artisan tinker
Post::all()->count()
Tag::all()->count()
PostTag::all()->count()

Read more about DB seeding in the official docs:
👉 https://laravel.com/docs/8.x/seeding

I hope this small project helped you get started with Laravel migrations, factories and seeding ✌
Think about this: a blog post usually contains more than just paragraphs. You can take the post model factory a few steps further and create rich random posts with images, quotes and more content elements ✍
The GitHub repository for this tutorial includes 2 Views for checking out your fake data. For now, these views are not styled at all and serve only as a quick reference for our data.