Real Time Comment System With Laravel And Vuejs

Today, Laravelcode share sommething new and very usefull tutorials with every one. Realtime comment system built using laravel and vuejs. comment system required every website. so, we are created here comment system with some awesome functionality. when user comment and post it then instently comment show in the page.

In this tutorials we are create one sapareted comment module and in this comment module we are cover following functionality. after user register then they will be able to do all functionality or action.

NOTE : In this tutorials we are not adding some file like css, we are cover some basiv stuff in this tutorials. if you get woring code so, please download from my github account we are also add link here Download working code

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

In this our tutorials for make comment system we are using following technologies :

  • Bootstrap
  • Loadash
  • Vue-resource
  • Laravel Mix

Now we are starting our comment system tutorials step by step.

Step : 1 Create comments table migration

First, we need to create comment table for stare all comment data and aslo span and comment voting data. so, create new one migration by following command.


php artisan make:migration comments

Now copy following migration code and past in your migration file.


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');
});

Now run your migration file using following command


php artisan migrate

Step : 2 Create Auth for Comment System

Now, we are created laravel built-in auth system using following command


php artisan make:auth

Step : 3 Create Models for the Comment System

Now, we are created model for comments table by run followign command


php artisan make:model Comment

Then open your app/Comment.php file and put into it following code.


namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
   	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 above code we are define required fillable columns fields with $fillable and create one replies() method which has the one-many relationship with own.

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


php artisan make:model CommentVote

Now open your app/CommentVote.php file and put into following code.


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;
}

Now, create last one model for comment_spam table


php artisan make:model CommentSpam

Now open your app/CommentSpam.php file and put into following code.


namespace App;

use Illuminate\Database\Eloquent\Model;

class CommentSpam extends Model
{
	protected $fillable = ['comment_id','user_id'];

	protected $table = "comment_spam";
 
	public $timestamps = false;
}

Step : 4 Create the Routes for Comments

Now, we are creating following route for required in out comment system. open your routes/web.php and past following route in it.


Route::get('/{pageId}', function($pageId) {
	return view('page',['pageId' => $pageId]);
});
// Route for index page
Route::get('comments/{pageId}', 'CommentController@index');
// Route for store comment
Route::post('comments', 'CommentController@store');
// Route for update comment
Route::post('comments/{commentId}/{type}', 'CommentController@update');

Step : 5 Create the Controller for Comment

Now, create controller for comment by run following command :


php artisan make:controller CommentController

After run this command then your controller file automatic generated in app/Http/Controllers/CommentController.php open it and place in it following code.


namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Comment;
use App\CommentVote;
use App\CommentSpam;

class CommentController extends Controller
{
	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');
   	}

	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 ];
   	}

	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";
       	}
   	}
}

Step : 6 Comment Component Using VueJS

Run following command for install all package.json file dependencies.


npm install

Now, we are required vue-resource so, run following command for it


npm install --save vue-resource

Now, go to the this path resources/assets/js/components and create one blank file Comments.vue. now leave it blank. and open your resources/assets/js/app.js and add following code.

Add the following line after require('./bootstrap');


import VueResource from "vue-resource"

Now, Add the following line after window.Vue = require('vue');


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

Step : 6 Write Code For Comments.vue

We are already create Comments.vue file before but then we are not put any code in it so, open Comments.vue file and put into following templet code


<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 :value="user.name">
                </div>
                <div class="form-row">
                    <input type="button" class="btn btn-success" @click="saveComment" value="Add Comment">
                </div>
            </form>
        </div>

        <div class="comment-form" v-else>
            <!-- Comment Avatar -->
            <div class="comment-avatar">
                <img src="storage/commentbox.png">
            </div>

            <form class="form" name="form">
                <div class="form-row">
                    <a href="login">
                        <textarea class="input" placeholder="Add comment..." required></textarea>
                    </a>
                </div>
            </form>
        </div>
        <!-- Comments List -->
        <div class="comments" v-if="comments" v-for="(comment,index) in commentsData">
            <!-- Comment -->
            <div v-if="!spamComments[index] || !comment.spam" class="comment">
                <!-- Comment Avatar -->
                <div class="comment-avatar">
                    <img src="storage/comment.png">
                </div>
                <!-- Comment Box -->
                <div class="comment-box">
                    <div class="comment-text">{{comment.comment}}</div>
                    <div class="comment-footer">
                        <div class="comment-info">
                            <span class="comment-author">
                               <em>{{ comment.name}}</em>
                            </span>
                            <span class="comment-date">{{ comment.date}}</span>
                        </div>
                        <div class="comment-actions">
                            <ul class="list">
                                <li>Votes: {{comment.votes}}
                                    <a v-if="!comment.votedByUser" v-on:click="voteComment(comment.commentid,'directcomment',index,0,'up')">Up Votes</a>
                                    <a v-if="!comment.votedByUser" v-on:click="voteComment(comment.commentid,'directcomment',index,0,'down')">Down Votes</a>
                                </li>
                                <li>
                                    <a v-on:click="spamComment(comment.commentId,'directcomment',index,0)">Spam</a>
                                </li>
                                <li>
                                    <a v-on:click="openComment(index)">Reply</a>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>=
                <!-- From -->
                <div class="comment-form comment-v" v-if="commentBoxs[index]">
                    <!-- Comment Avatar -->
                    <div class="comment-avatar">
                        <img src="storage/comment.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="errorReply" style="color:red">{{errorReply}}</span>
                        </div>
                        <div class="form-row">
                            <input class="input" placeholder="Email" type="text" :value="user.name">
                        </div>
                        <div class="form-row">
                            <input type="button" class="btn btn-success" v-on:click="replyComment(comment.commentid,index)" value="Add Comment">
                        </div>
                    </form>
                </div>
                <!-- Comment - Reply -->
                <div v-if="comment.replies">
                    <div class="comments" v-for="(replies,index2) in comment.replies">
                        <div v-if="!spamCommentsReply[index2] || !replies.spam" class="comment reply">
                            <!-- Comment Avatar -->
                            <div class="comment-avatar">
                                <img src="storage/comment.png">
                            </div>
                            <!-- Comment Box -->
                            <div class="comment-box" style="background: grey;">
                                <div class="comment-text" style="color: white">{{replies.comment}}</div>
                                <div class="comment-footer">
                                    <div class="comment-info">
                                        <span class="comment-author">
                                           {{replies.name}}
                                        </span>
                                        <span class="comment-date">{{replies.date}}</span>
                                    </div>
                                    <div class="comment-actions">
                                        <ul class="list">
                                            <li>Total votes: {{replies.votes}}
                                                <a v-if="!replies.votedByUser" v-on:click="voteComment(replies.commentid,'replycomment',index,index2,'up')">Up Votes</a>
                                                <a v-if="!replies.votedByUser" v-on:click="voteComment(comment.commentid,'replycomment',index,index2,'down')">Down Votes</a>
                                                </a>
                                            </li>
                                            <li>
                                                <a v-on:click="spamComment(replies.commentid,'replycomment',index,index2)">Spam</a>
                                            </li>
                                            <li>
                                                <a v-on:click="replyCommentBox(index2)">Reply</a>
                                            </li>
                                        </ul>
                                    </div>
                                </div>
                            </div>
                            <!-- From -->
                            <div class="comment-form reply" v-if="replyCommentBoxs[index2]">
                                <!-- Comment Avatar -->
                                <div class="comment-avatar">
                                    <img src="storage/comment.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="errorReply" style="color:red">{{errorReply}}</span>
                                    </div>
                                    <div class="form-row">
                                        <input class="input" placeholder="Email" type="text" :value="user.name">
                                    </div>
                                    <div class="form-row">
                                        <input type="button" class="btn btn-success" v-on:click="replyComment(comment.commentid,index)" value="Add Comment">
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

Step : 6 Add Script For Comments.vue

Now add following script code in Comments.vue file after closing template tag.


<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>

NOTE : In this tutorials we are not adding some file like css, we are cover some basiv stuff in this tutorials. if you get woring code so, please download from my github account we are also add link here Download working code

Now we are ready to run our example so run bellow command ro quick run:

php artisan serve

Now you can open bellow URL on your browser:

http://localhost:8000

If you face any problem then please write a comment or give some suggestions for improvement. Thanks...

 Prev post

Next post