To build trust in your online community or blog, one crucial element you’ll want is a well-designed Laravel live commenting system.

However, it’s not easy to get it right on the first try unless you rely on self-hosted commenting systems such as Disqus or Commento, each of which comes with its own set of disadvantages. They own your data, offer limited designs and customizations, and most importantly, they aren’t free.

With these limitations, if the idea of building your real-time commenting system — with the benefits of controlling your data, designing and customizing the look and feel to fit into your blog — appeals to you, keep reading.

This article will teach you how to develop a well-designed and real-time commenting system with different commenting functionalities. Following the principles of building a real-time chat application with Vue.js and Socket.io, we’ll use Laravel, Pusher, and React to develop the real-time commenting system.

Let’s dive in!

What We’ll Build

We’ll build a real-time commenting system that can be integrated into any website or blog to build trust in the community.

Building Blocks Overview: Laravel, Pusher, and Vue

Before we dive into the development, let’s discuss the technologies we’ll use to develop our real-time commenting system.

Laravel

Laravel is an open-source MVC-oriented PHP framework. It’s used to build simple to complex PHP web applications known for their elegant syntax. Learning what Laravel is essential to building this commenting system.

Pusher

Pusher enables developers to create real-time features at scale. This article will combine Laravel Echo to create a real-time broadcast event to the Pusher server and display the content on the frontend with Vue.js.

Vue.js

Vue.js is our frontend framework of choice. Vue.js is a progressive JavaScript frontend framework known for its easy-to-learn and straightforward approach to frontend development. We’ll be using Vue.js to develop our real-time commenting system.

Building the Commenting System

If the commenting system we’ve outlined above sounds like what you want, let’s move on to building it out.

1. Install and Setup Laravel, Pusher, and Echo

The installation and setting up of Laravel, Echo, and Pusher is straightforward as Laravel has done all the background tasks by setting up and configuring Laravel Echo to work with Pusher perfectly.

Firstly, we’ll start by installing and configuring Laravel, our backend PHP framework. You can grab a new instance of Laravel with this command, provided you’ve installed the Laravel CLI globally:

laravel new commenter

Your new Laravel instance will be installed in a folder called commenter. Let’s open the folder in our VSCode and navigate to it in our terminal:

cd commenter

code .

Before we start our development server, let’s install and configure some necessary packages that will be used for the project.

Run this command to install the Pusher PHP SDK:

composer require pusher/pusher-php-server

Run this command to install the necessary NPM packages for the Vue.js frontend:

npm install --save laravel-echo pusher-js

Next, we’ll configure the Laravel Echo and Pusher. Open your resources/js/bootstrap.js file and paste in the following scripts:

window._ = require("lodash");
window.axios = require("axios");
window.moment = require("moment");
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
window.axios.defaults.headers.post["Content-Type"] =
    "application/x-www-form-urlencoded";
window.axios.defaults.headers.common.crossDomain = true;
window.axios.defaults.baseURL = "/api";
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
    window.axios.defaults.headers.common["X-CSRF-TOKEN"] = token.content;
} else {
    console.error("CSRF token not found");
}


/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that Laravel broadcasts. Echo and event broadcasting
 * allows your team to build robust real-time web applications quickly.
 */
import Echo from "laravel-echo";
window.Pusher = require("pusher-js");
window.Echo = new Echo({
    broadcaster: "pusher",
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});

You’ll notice from the script above that we’re just configuring Axios instance with our default configurations. Next, we’ll configure Laravel Echo to use Pusher and its configurations.

2. Database Setup and Migration

Next, we’re going to create and set up our database to store the comments for persistence. We’ll be using SQLite, though you can use any database client of your choice.

Create a database.sqlite file inside the database folder and update your .env file as follows:

DB_CONNECTION=sqlite
DB_DATABASE=/Users/all/paths/to/project/commenter_be/database/database.sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=

Next, run this command to create the Comment migration and update it with the following scripts:

php artisan make:migration create_comments_table

Open the database/migrations/xxxx_create_comments_table_xxxx.php file and paste in this code:

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

This will create a new comments database table and add content and author columns.

Finally, to create the migration, run this command:

php artisan migrate

3. Creating Models

In Laravel, models are significant — they’re the surest way to communicate with our database and handle data management.

To create a model in Laravel, we’ll run the following command:

php artisan make:model Comment

Next, open the app/models/Comment.php file and paste in the following code:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
    use HasFactory;
    protected $fillable = ['content', 'author'];
}

The $fillable array allows us to create and update the model in mass.

4. Creating Controllers

Controllers are crucial because they house all the logic, business, and otherwise, of our applications, so let’s create one to handle the commenting logic:

php artisan make:controller CommentController

Next, open the app/Http/Controllers/CommentController.php file and paste in the following code:

<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use App\Events\CommentEvent;
use Illuminate\Http\Request;

class CommentController extends Controller
{
    //
    public function index()
    {
        return view('comments');
    }
    public function fetchComments()
    {
        $comments = Comment::all();
        return response()->json($comments);
    }
    public function store(Request $request)
    {
        $comment = Comment::create($request->all());
        event(new CommentEvent($comment));
        return $comment;
    }
}

The controller has three different methods: return a comment view, fetch all the comments, and store a new comment, respectively. Most importantly, we fire up an event each time we store a new comment, which the frontend will listen for to update the relevant page with the new comment in real-time using Pusher and Laravel Echo.

5. Creating Routes

To configure our routes properly, we’ll need to update lots of files, so let’s get started.

Firstly, we’re going to update the api.php file in the routes folder. Open the file and add the following code:

use App\Http\Controllers\CommentController;
//...

Route::get('/', [CommentController::class, 'index']);
Route::get('/comments', [CommentController::class, 'fetchComments']);
Route::post('/comments', [CommentController::class, 'store']);

Next, open the channels.php file in the same folder and add the following code to authorize the event we fired earlier:

Broadcast::channel('comment', function ($user) {
    return true;
});

Next, open the web.php file in the same folder and add the following code to redirect our request to the homepage, where Vue.js will pick it up:

use App\Http\Controllers\CommentController;
//...

Route::get('/', [CommentController::class, 'index']);

Lastly, we’ll create a new blade file in the resources/views folder called comments.blade.php and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Commenter</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css" />
    <style>
        .container {
            margin: 0 auto;
            position: relative;
            width: unset;
        }
        #app {
            width: 60%;
            margin: 4rem auto;
        }
        .question-wrapper {
            text-align: center;
        }
    </style>
</head>
<body>


    <div id="app">
        <div class="container">
            <div class="question-wrapper">
                <h5 class="is-size-2" style="color: #220052;">
                    What do you think about <span style="color: #47b784;">Dogs</span>?</h5>
                <br>
                <a href="#Form" class="button is-medium has-shadow has-text-white" style="background-color: #47b784">Comment</a>
            </div>
            <br><br>
            <comments></comments>
            <new-comment></new-comment>
        </div>
    </div>
    <script async src="{{mix('js/app.js')}}"></script>
</body>
</html>

The script adds a post title and a Vue component to display and add new comments to the post title created above.

Run the following commands to test if you get everything correctly:

npm run watch

php artisan serve

If you’re presented with this page, you’re ready to move to the next step in this article.

Live commenting system in Laravel
Live commenting system in Laravel

6. Setting Up Vue (Frontend)

We’ll create and set up our Vue instance to create and display all the comments made on this post.

We’ll start with setting up our Vuex store. Create the following files in the resource/js/store folder.

Create Comment State

Create actions.js and add the following code:

let actions = {
    ADD_COMMENT({ commit }, comment) {
        return new Promise((resolve, reject) => {
            axios
                .post(`/comments`, comment)
                .then(response => {
                    resolve(response);
                })
                .catch(err => {
                    reject(err);
                });
        });
    },
    GET_COMMENTS({ commit }) {
        axios
            .get("/comments")
            .then(res => {
                {
                    commit("GET_COMMENTS", res.data);
                }
            })
            .catch(err => {
                console.log(err);
            });
    }
};
export default actions;

The Action file makes a call to the comment endpoint in the backend.

Next, create a getters.js file and add the following code:

let getters = {
    comments: state => {
        return state.comments;
    }
};
export default getters;

The Getter file is used to retrieve all the comments in the state.

Create the mutations.js file and paste it into the following code:

let mutations = {
    GET_COMMENTS(state, comments) {
        state.comments = comments;
    },
    ADD_COMMENT(state, comment) {
        state.comments = [...state.comments, comment];
    }
};
export default mutations;

Next, create a state.js file and paste it into the following code:

let state = {
    comments: []
};
export default state;

Lastly, we’ll add everything to the index.js file exported to the Vue instance, create an index.js file and add the following:

import Vue from "vue";
import Vuex from "vuex";
import actions from "./actions";
import mutations from "./mutations";
import getters from "./getters";
import state from "./state";
Vue.use(Vuex);
export default new Vuex.Store({
    state,
    mutations,
    getters,
    actions
});

Create Components

Lastly, we’re going to create our comment components to display and add new comments. Let’s start by creating the single comment component.

Create a folder in the resource/js folder called components, add the comment.vue and add the following code:

<template>
  <li class="comment-wrapper animate slideInLeft">
    <div class="profile">
    </div>
    <div class="msg has-shadow">
      <div class="msg-body">
        <p class="name">
          {{ comment.author }} <span class="date">{{ posted_at }}</span>
        </p>
        <p class="content">{{ comment.content }}</p>
      </div>
    </div>
  </li>
</template>
    
    <script>
export default {
  name: "Comment",
  props: ["comment"],
  computed: {
    posted_at() {
      return moment(this.comment.created_at).format("MMMM Do YYYY");
    },

  },
};
</script>
    
    <style lang="scss" scoped>
.comment-wrapper {
  list-style: none;
  text-align: left;
  overflow: hidden;
  margin-bottom: 2em;
  padding: 0.4em;
  .profile {
    width: 80px;
    float: left;
  }
  .msg-body {
    padding: 0.8em;
    color: #666;
    line-height: 1.5;
  }
  .msg {
    width: 86%;
    float: left;
    background-color: #fff;
    border-radius: 0 5px 5px 5px;
    position: relative;
    &::after {
      content: " ";
      position: absolute;
      left: -13px;
      top: 0;
      border: 14px solid transparent;
      border-top-color: #fff;
    }
  }
  .date {
    float: right;
  }
  .name {
    margin: 0;
    color: #999;
    font-weight: 700;
    font-size: 0.8em;
  }
  p:last-child {
    margin-top: 0.6em;
    margin-bottom: 0;
  }
}
</style>

Next, create the following file called comments.vue in the same folder and add the following code:

<template>
  <div class="container">
    <ul class="comment-list">
      <Comment
        :key="comment.id"
        v-for="comment in comments"
        :comment="comment"
      ></Comment>
    </ul>
  </div>
</template>
    
    <script>
import { mapGetters } from "vuex";
import Comment from "./Comment";
export default {
  name: "Comments",
  components: { Comment },
  mounted() {
    this.$store.dispatch("GET_COMMENTS");
    this.listen();
  },
  methods: {
    listen() {
      Echo.channel("comment").listen("comment", (e) => {
        console.log(e);
        this.$store.commit("ADD_COMMENT", e);
      });
    },
  },
  computed: {
    ...mapGetters(["comments"]),
  },
};
</script>
    
    <style scoped>
.comment-list {
  padding: 1em 0;
  margin-bottom: 15px;
}
</style>

Lastly, create a file called NewComment.vue and add the following code:

<template>
  <div id="commentForm" class="box has-shadow has-background-white">
    <form @keyup.enter="postComment">
      <div class="field has-margin-top">
        <div class="field has-margin-top">
          <label class="label">Your name</label>
          <div class="control">
            <input
              type="text"
              placeholder="Your name"
              class="input is-medium"
              v-model="comment.author"
            />
          </div>
        </div>
        <div class="field has-margin-top">
          <label class="label">Your comment</label>
          <div class="control">
            <textarea
              style="height: 100px"
              name="comment"
              class="input is-medium"
              autocomplete="true"
              v-model="comment.content"
              placeholder="lorem ipsum"
            ></textarea>
          </div>
        </div>
        <div class="control has-margin-top">
          <button
            style="background-color: #47b784"
            :class="{ 'is-loading': submit }"
            class="button has-shadow is-medium has-text-white"
            :disabled="!isValid"
            @click.prevent="postComment"
            type="submit"
          >
            Submit
          </button>
        </div>
      </div>
    </form>
    <br />
  </div>
</template>
    
    <script>
export default {
  name: "NewComment",
  data() {
    return {
      submit: false,
      comment: {
        content: "",
        author: "",
      },
    };
  },
  methods: {
    postComment() {
      this.submit = true;
      this.$store
        .dispatch("ADD_COMMENT", this.comment)
        .then((response) => {
          this.submit = false;
          if (response.data) console.log("success");
        })
        .catch((err) => {
          console.log(err);
          this.submit = false;
        });
    },
  },
  computed: {
    isValid() {
      return this.comment.content !== "" && this.comment.author !== "";
    },
  },
};
</script>
    
    <style scoped>
.has-margin-top {
  margin-top: 15px;
}
</style>

Now, open the app.js file and add the following code to register the Vue components you created earlier:

// resource/js/app.js

require("./bootstrap");
window.Vue = require("vue");
import store from "./store/index";

Vue.component("comment", require("./components/Comment"));
Vue.component("comments", require("./components/Comments"));
Vue.component("new-comment", require("./components/NewComment"));

const app = new Vue({
    el: "#app",
    store
});

Summary

And that’s it! You’ve just learned how to build a live commenting system for your site using Laravel.

We’ve discussed the benefits of creating and managing a commenting system in your quest to build trust in your community or blog. We’ve also explored how to develop a well-designed and real-time commenting system from the ground up, utilizing different commenting functionalities.

You can clone the source code of this project in this Github repo.

What do you think of the Laravel live commenting system we’ve built together? Let us know in the comments!

 

Solomon Eseme

I am a Software Engineer and Content Creator who is geared toward building high-performing and innovative products following best practices and industry standards. I also love writing about it at Masteringbackend.com. Follow me on Twitter, LinkedIn, and About Me