X
    Categories: Learn Laravel Tutorials, Tips And Guides

Create a Comments System with Laravel And Vuejs

In my previous article on Laravel and Vue.js, I created a realtime chatroom using Laravel, Vuej.s and Pusher. Thank you all for your kind words and the 60 stars on the GitHub repository for this.

Today, I will use Laravel and Vue.js to create something that is useful at every website – a Laravel comment system. This system is not as real time as the chatroom but you will be able to see the new comments and replies for a post/page instantly.

I will create a separate comment component in view which can be added to any web page and blog post. Registered members of the system could perform the following  actions:

  • Add new comments
  • Reply to comments
  • Up and Down votes
  • Mark spam comments

This system will be based on the following technologies:

  • Bootstrap
  • Loadash
  • Vue-resource
  • Laravel Mix

The complete code for this comment system can be found in the github repo. Hear here for a live demo of the application.

Create Migration Tables for Comments

First, I need to create tables in which I am going to save the comments, mark comments spam and save the records for voting.

Start with the following command to create a new migration file:

php artisan make:migration comments

Now open the newly created file and paste the following code inside the `Up()` method:

Schema::create('comments', function (Blueprint $table) {

           $table->increments('id');

           $table->text('comment');

           $table->integer('votes')->default(0);

           $table->integer('spam')->default(0);

           $table->integer('reply_id')->default(0);

           $table->string('page_id')->default(0);

           $table->integer('users_id');

           $table->timestamps();

       });



       Schema::create('comment_user_vote', function (Blueprint $table) {

           $table->integer('comment_id');

           $table->integer('user_id');

           $table->string('vote',11);

       });



       Schema::create('comment_spam', function (Blueprint $table) {

           $table->integer('comment_id');

           $table->integer('user_id');

       });

The schema for the tables are simple. First I created the comments table in which I am going to save the comments. In the second table (comment_user_vote), I am going to save the users who have voted on the comments. In the last table, comment_spam, I will save the comment id of the comments that the users have marked as spam.

Now, run the following command to migrate all the tables:

php artisan migrate

Create Auth for Comment System

I will create the Laravel built-in auth so that only the registered user can add comments.

Start with the following command:

php artisan make:auth

Create Models for the Comment System

Let’s first create the model for the comments table.

Start with the following command to create the model:

php artisan make:model Comment

Open the newly created file (located in the app folder). Replace the existing code with the following code:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model

{

   //

   /**

    * Fillable fields for a course

    *

    * @return array

    */

   protected $fillable = ['comment','votes','spam','reply_id','page_id','users_id'];

   protected $dates = ['created_at', 'updated_at'];

   public function replies()

   {

       return $this->hasMany('App\Comment','id','reply_id');

   }

}

In the above code I defined the `$fillable` columns, `$dates` columns of the table and created `replies()` method which has the one-many relationship with its own table.

Now create a model for comment_user_vote. Run the following command:

php artisan make:model CommentVote

Now open the new created file and replace the existing code with the following:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class CommentVote extends Model

{

   //

   protected $fillable = ['comment_id','user_id','vote'];

   protected $table = "comment_user_vote";

   public $timestamps = false;

}

Next, create the model for the last table comment_spam through the following command:

php artisan make:model CommentSpam

Open the file and replace the existing code with the following code:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class CommentSpam extends Model

{

   //

   protected $fillable = ['comment_id','user_id'];

   protected $table = "comment_spam";

   public $timestamps = false;

}

Create the Controller for Comment

To create a controller for comment, run the following command:

php artisan make:controller CommentController

Open the newly created file and start creating methods in it.

Save a new Comment

Start by adding a method which will save a new comment or reply:

/**

    * Store a newly created resource in storage.

    *

    * @param  \Illuminate\Http\Request  $request

    * @return \Illuminate\Http\Response

    */

   public function store(Request $request)

   {

       $this->validate($request, [

       'comment' => 'required',

       'reply_id' => 'filled',

       'page_id' => 'filled',

       'users_id' => 'required',

       ]);

       $comment = Comment::create($request->all());

       if($comment)

           return [ "status" => "true","commentId" => $comment->id ];

   }

The above code is pretty simple as I am getting the inputs. The code also makes sure that they exists and then save it all.

Voting And Marking Comments as Spam

Let’s now create a new function through which I can handle the upvoting and downvoting of comments.This will also handle the marking of comments as spam.

   /**

    * Update the specified resource in storage.

    *

    * @param  \Illuminate\Http\Request  $request

    * @param  $commentId

    * @param  $type

    * @return \Illuminate\Http\Response

    */

   public function update(Request $request, $commentId,$type)

   {

       if($type == "vote"){          

           $this->validate($request, [

           'vote' => 'required',

           'users_id' => 'required',

           ]);



           $comments = Comment::find($commentId);

           $data = [

               "comment_id" => $commentId,

               'vote' => $request->vote,

               'user_id' => $request->users_id,

           ];


           if($request->vote == "up"){

               $comment = $comments->first();

               $vote = $comment->votes;

               $vote++;

               $comments->votes = $vote;

               $comments->save();

           }


           if($request->vote == "down"){

               $comment = $comments->first();

               $vote = $comment->votes;

               $vote--;

               $comments->votes = $vote;

               $comments->save();

           }


           if(CommentVote::create($data))

               return "true";

       }

       if($type == "spam"){
          
           $this->validate($request, [

               'users_id' => 'required',

           ]);

           $comments = Comment::find($commentId);

           $comment = $comments->first();

           $spam = $comment->spam;

           $spam++;

           $comments->spam = $spam;

           $comments->save();

           $data = [

               "comment_id" => $commentId,

               'user_id' => $request->users_id,

           ];



           if(CommentSpam::create($data))

               return "true";

       }

   }

The above function has three parameters. The first parameter is the request which is incoming, the second one is the comment Id, and the third is the type. Through the `$type` variable, I will check the user action (vote the comment or mark it spam). Next, according to the type, the request is validated. If the type is vote, I first get the comment and then check the user intention ( i.e up or downvote the comment). Then I perform the operation and save the comment.  Marking a comment as spam follows a similar process.

Get the Comments

Next, I will create a function that will get all the comments and the replies.

/**

    * Get Comments for pageId

    *

    * @return Comments

    */

   public function index($pageId)

   {

       //

       $comments = Comment::where('page_id',$pageId)->get();

       $commentsData = []

       foreach ($comments as $key) {

           $user = User::find($key->users_id);

           $name = $user->name;

           $replies = $this->replies($key->id);

           $photo = $user->first()->photo_url;

           // dd($photo->photo_url);

           $reply = 0;

           $vote = 0;

           $voteStatus = 0;

           $spam = 0;

           if(Auth::user()){

               $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

               $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();              

               if($voteByUser){

                   $vote = 1;

                   $voteStatus = $voteByUser->vote;

               }

               if($spamComment){

                   $spam = 1;

               }

           }          

           if(sizeof($replies) > 0){

               $reply = 1;

           }

           if(!$spam){

               array_push($commentsData,[

                   "name" => $name,

                   "photo_url" => (string)$photo,

                   "commentid" => $key->id,

                   "comment" => $key->comment,

                   "votes" => $key->votes,

                   "reply" => $reply,

                   "votedByUser" =>$vote,

                   "vote" =>$voteStatus,

                   "spam" => $spam,

                   "replies" => $replies,

                   "date" => $key->created_at->toDateTimeString()

               ]);

           }       

       }

       $collection = collect($commentsData);

       return $collection->sortBy('votes');

   }

   protected function replies($commentId)

   {

       $comments = Comment::where('reply_id',$commentId)->get();

       $replies = [];

       foreach ($comments as $key) {

           $user = User::find($key->users_id);

           $name = $user->name;

           $photo = $user->first()->photo_url;

           $vote = 0;

           $voteStatus = 0;

           $spam = 0;        

           if(Auth::user()){

               $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

               $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

               if($voteByUser){

                   $vote = 1;

                   $voteStatus = $voteByUser->vote;

               }

               if($spamComment){

                   $spam = 1;

               }

           }

           if(!$spam){        

               array_push($replies,[

                   "name" => $name,

                   "photo_url" => $photo,

                   "commentid" => $key->id,

                   "comment" => $key->comment,

                   "votes" => $key->votes,

                   "votedByUser" => $vote,

                   "vote" => $voteStatus,

                   "spam" => $spam,

                   "date" => $key->created_at->toDateTimeString()

               ]);

       }

       $collection = collect($replies);

       return $collection->sortBy('votes');

   }

The above code is simple, except for this part:

if(Auth::user()){

               $voteByUser = CommentVote::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();

               $spamComment = CommentSpam::where('comment_id',$key->id)->where('user_id',Auth::user()->id)->first();         

               if($voteByUser){

                   $vote = 1;

                   $voteStatus = $voteByUser->vote;

               }

               if($spamComment){

                   $spam = 1;

               }

           }

In this snippet, I’m first checking whether the user is logged in. Next, I will check whether he have voted a comment or marked it spam. If he have voted on a comment, I will save his (up or down) vote.  Next, I will check whether the user has marked any comment as spam. If  yes, then I will set the boolean `$spam` to true so that the spam comment will not be visible to the user. The `replies()` method will get the replies for that comment.

Create the Routes for Comments

Go to the routes folder and open the web.php file. Add the following routes to it:

Route::get('/{pageId}', function($pageId){

   return view('page',['pageId' => $pageId]);

});

Route::get('comments/{pageId}', 'CommentController@index');

Route::post('comments', 'CommentController@store');

Route::post('comments/{commentId}/{type}', 'CommentController@update');

At this point, the backend of the comment system is done. I will now create the frontend for displaying comments.

Comment Component Using VueJS

For of all, run the following command to install the required dependencies:

npm install

Once done, run the following command to install vue-resource.

npm install -s vue-resource

Now head to resources/assets/js/components and create a new file Comments.vue. Leave the file empty for now. Now go back a step and open app.js to add new reference.

Add the following line after `require(‘./bootstrap’)`:

import VueResource from "vue-resource"

After the Vue instance, add the following line:

Vue.use(VueResource);

And after the `Vue.component` add this line:

Vue.component('comment', require('./components/Comments.vue'));

Create the Comment Component Template

Open the comment.vue file (created earlier) and add the comment template in it. Paste the following code in it:

<template>

<div class="comments-app">

   <h1>Comments</h1>

   <!-- From -->

   <div class="comment-form" v-if="user">

       <!-- Comment Avatar -->

       <div class="comment-avatar">

           <img src="storage/commentbox.png">

       </div>



       <form class="form" name="form">

           <div class="form-row">

               <textarea class="input" placeholder="Add comment..." required v-model="message"></textarea>

               <span class="input" v-if="errorComment" style="color:red">{{errorComment}}</span>

           </div>



           <div class="form-row">

               <input class="input" placeholder="Email" type="text" disabled :>
In this template, I have created the form for adding comments. I have also added a condition that if the user is logged in, then the button will be visible, else he will be redirected to the login page.

Next are the divs, where comments and the replies will be displayed. I have also added the onclick methods for vote up, vote down and mark spam buttons. In this template, if a user wants to reply to a comment, a form will open up for posting the comment.

Let’s now create a script and add these methods in it.

Create a Comment Component Script

Now after the closing tag of </template>, paste the following lines of code:
<script>

var _ = require('lodash');

export default {

   props: ['commentUrl'],

   data() {

       return {

           comments: [],

           commentreplies: [],

           comments: 0,

           commentBoxs: [],

           message: null,

           replyCommentBoxs: [],

           commentsData: [],

           viewcomment: [],

           show: [],

           spamCommentsReply: [],

           spamComments: [],

           errorComment: null,

           errorReply: null,

           user: window.user

       }

   },

   http: {

       headers: {

           'X-CSRF-TOKEN': window.csrf

       }

   },

   methods: {

       fetchComments() {

           this.$http.get('comments/' + this.commentUrl).then(res => {



               this.commentData = res.data;

               this.commentsData = _.orderBy(res.data, ['votes'], ['desc']);

               this.comments = 1;

           });

           

       },

       showComments(index) {

           if (!this.viewcomment[index]) {

               Vue.set(this.show, index, "hide");

               Vue.set(this.viewcomment, index, 1);

           } else {

               Vue.set(this.show, index, "view");

               Vue.set(this.viewcomment, index, 0);

           }

       },

       openComment(index) {

           if (this.user) {

               if (this.commentBoxs[index]) {

                   Vue.set(this.commentBoxs, index, 0);

               } else {

                   Vue.set(this.commentBoxs, index, 1);

               }

           }

       },

       replyCommentBox(index) {

           if (this.user) {

               if (this.replyCommentBoxs[index]) {

                   Vue.set(this.replyCommentBoxs, index, 0);

               } else {

                   Vue.set(this.replyCommentBoxs, index, 1);

               }

           }

       },

       saveComment() {

           if (this.message != null && this.message != ' ') {

               this.errorComment = null;

               this.$http.post('comments', {

                   page_id: this.commentUrl,

                   comment: this.message,

                   users_id: this.user.id

               }).then(res => {



                   if (res.data.status) {

                       this.commentsData.push({ "commentid": res.data.commentId, "name": this.user.name, "comment": this.message, "votes": 0, "reply": 0, "replies": [] });

                       this.message = null;

                   }



               });

           } else {

               this.errorComment = "Please enter a comment to save";

           }

       },

       replyComment(commentId, index) {

           if (this.message != null && this.message != ' ') {

               this.errorReply = null;

               this.$http.post('comments', {

                   comment: this.message,

                   users_id: this.user.id,

                   reply_id: commentId

               }).then(res => {



                   if (res.data.status) {

                       if (!this.commentsData[index].reply) {

                           this.commentsData[index].replies.push({ "commentid": res.data.commentId, "name": this.user.name, "comment": this.message, "votes": 0 });

                           this.commentsData[index].reply = 1;

                           Vue.set(this.replyCommentBoxs, index, 0);

                           Vue.set(this.commentBoxs, index, 0);

                       } else {

                           this.commentsData[index].replies.push({ "commentid": res.data.commentId, "name": this.user.name, "comment": this.message, "votes": 0 });

                           Vue.set(this.replyCommentBoxs, index, 0);

                           Vue.set(this.commentBoxs, index, 0);

                       }

                       this.message = null;

                   }



               });

           } else {

               this.errorReply = "Please enter a comment to save";

           }

       },

       voteComment(commentId, commentType, index, index2, voteType) {

           if (this.user) {

               this.$http.post('comments/' + commentId + '/vote', {

                   users_id: this.user.id,

                   vote: voteType

               }).then(res => {

                   if (res.data) {

                       if (commentType == 'directcomment') {

                           if (voteType == 'up') {

                               this.commentsData[index].votes++;

                           } else if (voteType == 'down') {

                               this.commentsData[index].votes--;

                           }

                       } else if (commentType == 'replycomment') {

                           if (voteType == 'up') {

                               this.commentsData[index].replies[index2].votes++;

                           } else if (voteType == 'down') {

                               this.commentsData[index].replies[index2].votes--;

                           }

                       }

                   }



               });

           }

       },

       spamComment(commentId, commentType, index, index2) {

           console.log("spam here");

           if (this.user) {

               this.$http.post('comments/' + commentId + '/spam', {

                   users_id: this.user.id,

               }).then(res => {

                   if (commentType == 'directcomment') {

                       Vue.set(this.spamComments, index, 1);

                       Vue.set(this.viewcomment, index, 1);

                   } else if (commentType == 'replycomment') {

                       Vue.set(this.spamCommentsReply, index2, 1);

                   }

               });



           }



       },

   },

   mounted() {

      console.log("mounted");

      this.fetchComments();

   }



}

</script>

Here is a brief explanation of the code above. In the first line, I have required the lodash library to filter comment by vote. Next, I have defined props for commentUrl and then added variables in the data().

Following are the most important methods:

fetchComments()

This method will run when the component is mounted to fetch all the comments for that page. This is why it is used inside `mounted()`.

openComment(index)

This method will open reply comment box for a comment. This method is called by the reply button.

openComment(index) {

           if (this.user) {

               if (this.commentBoxs[index]) {

                   Vue.set(this.commentBoxs, index, 0);

               } else {

                   Vue.set(this.commentBoxs, index, 1);

               }

           }

       }

The method first checks if the user is logged in, and then opens the reply box. If the box is already open, it will close the box.

replyCommentBox(index)

This method is similar to `openComment()`. The difference is that this method opens the comment box for the replies of comments. Check out the image below:

saveComment()

This function will save the user comment.

saveComment() {

           if (this.message != null && this.message != ' ') {

               this.errorComment = null;

               this.$http.post('comments', {

                   page_id: this.commentUrl,

                   comment: this.message,

                   users_id: this.user.id

               }).then(res => {



                   if (res.data.status) {

                       this.commentsData.push({ "commentid": res.data.commentId, "name": this.user.name, "comment": this.message, "votes": 0, "reply": 0, "replies": [] });

                       this.message = null;

                   }



               });

           } else {

               this.errorComment = "Please enter a comment to save";

           }

       }

This function first checks if the comment is null. If it's null, it will display the error message. If not, it will first bind the params and then send the post request to `comments/`. Once successful, it will push the comment to `commentsData` array so that it can be displayed in the page instantly.

replyComment(commentId, index)

The reply comment saves the reply for the parent comment. It works similar to `saveComment()`. The difference is that it takes `commentId` and the index to bind the reply comment once it is saved.

voteComment(commentId, commentType, index, index2, voteType)

As the name implies, this method is used to vote comments. It first check whether the user is logged in  and then the user could comment. The parameters are defined as:

  • commentId: The id of the comment to vote
  • commentType: The direct comment or reply.
  • Index,index2: Are the indexes of array
  • voteType: Up or Down vote

spamComment(commentId, commentType, index, index2)

This method marks a comment as spam. It only works when the user is logged in. The parameters are defined as:

  • commentId: The id of the comment
  • commentType: The direct comment or reply.
  • Index,index2: Are the the indexes of array

I will now compile the component. For that, run the following command:

npm run dev

Testing Laravel Comment Component

Now open the app in browser. Give it a pageId, for instance “1235” so that the browser URL will be “public/1235”. Once the page opens, you will see the following page:

Since there are no comment yet, it will be empty.

Now click on the Add Comment and you will be redirected to the login page. Register yourself and then head back to the page to add a comment.

Let’s add a new comment:

When the page is reloaded, you will see the comment:

Now add replies and more comments to the page.

Winding it Up

Let’s summarize it all. I have created a Laravel comment system using Vue.js through which you can save, reply, vote and mark comments spam. Let me know if you have any issue in understanding the code. Leave a comment below and I will get back to you.

Ahmed Khan: Ahmed was a PHP community expert at Cloudways - A Managed PHP Hosting Cloud Platform. He is a software engineer with extensive knowledge in PHP and SEO. He loves watching Game of Thrones is his free time. Follow Ahmed on Twitter to stay updated with his works. You can email him at ahmed.khan@cloudways.com