Community Post

Build nested commenting system using Laravel and VueJs - Part 1

jagadeshanh
👁️ 0 views
💬 comments

In this tutorial will learn how to build nested comments or threaded comments or recursive comments using Laravel and VueJs.

In this tutorial will cover the following topics:

In the part 1 of this tutorial will cover

Table of Contents

    1. Creating new laravel project and basic setup
    2. Creating models and migrations
    3. Creating controllers
    4. Using passport for api authentication

    in the part 2 and 3 of this tutorial will cover

    1. Creating vue components for our post and comments

    # Creating new laravel project and basic setup

    Laravel utilizes Composer to manage its dependencies, meaning you can install any laravel package using composer and hence its better to setup composer first.

    If you are on windows machine download composer and run Composer-Setup.exe and you are done.

    If you are on ubuntu then run the following commands in your terminal

    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    php -r "if (hash_file('SHA384', 'composer-setup.php') === '55d6ead61b29c7bdee5cccfb50076874187bd9f21f65d8991d46ec5cc90518f447387fb9f76ebae1fbbacf329e583e30') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
    php composer-setup.php
    php -r "unlink('composer-setup.php');"

    Once you have installed composer test it by typing composer in your terminal. If you are not getting any errors means your composer is ready to use, there are two options to install laravel, either by using laravel/installer or via Composer create-project command.

    via laravel installer: open your terminal and type

    composer global require "laravel/installer"
    laravel new project-name

    via composer create-project: open your terminal and type

    composer create-project --prefer-dist laravel/laravel project-name

    For example: let's create a project called recursive-comments and setup database and authentication. In your terminal type

    laravel new recursive-comments
    cd recursive-comments
    php artisan make:auth

    Once you have generated authentication scafoldings let's create a database and connect to it. In your terminal type, if you have a password then only type -p else just type mysql -u root

    mysql -u root -p
    password: enter-your-password
    mysql> create database comments;
    Query OK, 1 row affected (0.00 sec)

    one last step is to update your .env file, find the following lines and update to match your database settings, I am using mysql and running on port 3306 at localhost, and we just created a database called comments and username is root and even the password for my localsystem is root

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=comments
    DB_USERNAME=root
    DB_PASSWORD=root

    test your database connection by typing following commands inside your project directory

    cd recursive-comments
    php artisan migrate

    you should get output similar to the following.

    Migration table created successfully.
    Migrated: 2014_10_12_000000_create_users_table
    Migrated: 2014_10_12_100000_create_password_resets_table

    # Creating models and migrations

    In our next step will create two database migrations and two models called Post and Comment to store our posts and comments. In your terminal type the following artisan commands

    php artisan make:model Post -m
    php artisan make:model Comment -m

    this will create a model inside your app directory and also create corresponding migrations in your database/migrations folder. go to your create_posts_table migration and edit it

    <?php
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreatePostsTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('posts', function (Blueprint $table) {
                $table->increments('id');
                $table->integer('user_id')->unsigned();
                $table->text('content');
                $table->timestamps();
                $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('posts');
        }
    }
    

    now go to create_comments_table migration and edit it

    <?php
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateCommentsTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('comments', function (Blueprint $table) {
                $table->increments('id');
                $table->integer('user_id')->index();
                $table->integer('post_id')->index();
                $table->integer('parent_id')->index()->nullable();
                $table->text('content');
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('comments');
        }
    }

    to keep it very simple, we are just allowing user to post only text and user can comment on these post or reply to the comments. In our comment's table we have parent_id which is nullable, meaning we just have one table to hold all comments with parent_id, when the parent_id is null means its a root comment and not a reply to anything, if our parent_id has a value then it is a reply to the comment with that parent_id. For example consider the following data in table. In this example, user with id 1 has commented on a post with id 1 and the content is hey there!, and in the second row an user with id 2 has replied to this comment saying hello!. hence the first comment has parent_id null and the second has parent_id 1. Now let's migrate these two tables, go to terminal and type the following

    php artisan migrate

    and you should get an output similar to this

    Migrated: 2017_02_07_174102_create_posts_table
    Migrated: 2017_02_07_174112_create_comments_table

    Now let's setup our Post and Comment models with relations and to get threaded comments.

    Go to your Post model under app directory and edit it.

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class Post extends Model
    {
        protected $fillable = ['user_id', 'content'];
    
        public function user(){
            return $this->belongsTo(User::class);
        }
    
        public function comments()
        {
            return $this->hasMany(Comment::class);
        }
    
        public function getThreadedComments(){
            return $this->comments()->with('user')->get()->threaded();
        }
    
        public function addComment($attributes)
        {
            $comment = (new Comment())->forceFill($attributes);
            $comment->user_id = auth()->id();
            return $this->comments()->save($comment);
        }
    }
    

    In this model we have a relationship between user and the post, since every post is created by user they belong to them. And the second relation is comments, a post can have many comments, and since each comments can have replies we are defining one more function called getThreadedComments which will get replies to those comments along with the user. And finally we are adding a method addComment which will insert a new comment to that post.

    Now lets edit our Comment model

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class Comment extends Model
    {
        protected $fillable = ['content'];
    
        public function user()
        {
            return $this->belongsTo(User::class, 'user_id');
        }
    
        public function newCollection(array $models = [])
        {
            return new CommentCollection($models);
        }
    }

    Here we are again defining a relation between user and a comment and a newCollection method which will return collection of comments grouped by parent_id. Now lets create a CommentCollection class, create it under app directory.

    <?php
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Collection;
    
    class CommentCollection extends Collection
    {
        public function threaded()
        {
            $comments = parent::groupBy('parent_id');
            if (count($comments)) {
                $comments['root'] = $comments[''];
                unset($comments['']);
            }
            return $comments;
        }
    }
    

    # Creating controllers

    In this step will create two resource controllers inside Api directory, go to terminal inside your project directory and type

    php artisan make:controller Api/PostController --resource
    php artisan make:controller Api/CommentController --resource

    Now you will have two controllers created inside app/Http/Controllers/Api named PostController and CommentController

    # Using passport for api authentication

    You can study more about passport here. Passport is a laravel package and to use it we need to install it using composer, go to your project directory and type

    composer require laravel/passport

    you should get an output similar to the following go to config/app.php and add Laravel\Passport\PassportServiceProvider::class inside your providers array and then run php artisan migrate, this will create oauth2 tables for you. After running php artisan migrate in your terminal you should get an output similar to this.

    Migrated: 2016_06_01_000001_create_oauth_auth_codes_table
    Migrated: 2016_06_01_000002_create_oauth_access_tokens_table
    Migrated: 2016_06_01_000003_create_oauth_refresh_tokens_table
    Migrated: 2016_06_01_000004_create_oauth_clients_table
    Migrated: 2016_06_01_000005_create_oauth_personal_access_clients_table

    Now that we have setup tables, we need to create encryption keys, which will be used by laravel to create secure access tokens. to create keys in your terminal just type

    php artisan passport:install

    After running this command go to App/User.php and then add HasApiTokens trait.

    <?php
    
    namespace App;
    
    use Laravel\Passport\HasApiTokens;
    use Illuminate\Notifications\Notifiable;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    
    class User extends Authenticatable
    {
        use HasApiTokens, Notifiable;
    
        /**
         * The attributes that are mass assignable.
         *
         * @var array
         */
        protected $fillable = [
            'name', 'email', 'password',
        ];
    
        /**
         * The attributes that should be hidden for arrays.
         *
         * @var array
         */
        protected $hidden = [
            'password', 'remember_token',
        ];
    }
    

    next go to AuthServiceProvider and add Passport::routes(); inside boot function

    <?php
    
    namespace App\Providers;
    
    use Illuminate\Support\Facades\Gate;
    use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
    
    class AuthServiceProvider extends ServiceProvider
    {
        /**
         * The policy mappings for the application.
         *
         * @var array
         */
        protected $policies = [
            'App\Model' => 'App\Policies\ModelPolicy',
        ];
    
        /**
         * Register any authentication / authorization services.
         *
         * @return void
         */
        public function boot()
        {
            $this->registerPolicies();
            Passport::routes();
        }
    }
    

    Finally, go to config/auth.php edit api array driver and change it from token to passport

    'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
    
            'api' => [
                'driver' => 'passport',
                'provider' => 'users',
            ],
        ],

    One last step so that passport will take care of sending tokens for every request that we make.

    Go to App\Http\Kernal.php and add \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class to web middleware

    protected $middlewareGroups = [
            'web' => [
                \App\Http\Middleware\EncryptCookies::class,
                \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
                \Illuminate\Session\Middleware\StartSession::class,
                // \Illuminate\Session\Middleware\AuthenticateSession::class,
                \Illuminate\View\Middleware\ShareErrorsFromSession::class,
                \App\Http\Middleware\VerifyCsrfToken::class,
                \Illuminate\Routing\Middleware\SubstituteBindings::class,
                 \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
            ],
    
            'api' => [
                'throttle:60,1',
                'bindings',
            ],
        ];

    Part 2