Learn how you can build a live comment feature using Laravel and Pusher. This will allow users to see new comments without reloading the page.
In this tutorial, we will create a live commenting system using Laravel and Pusher. With the release of Echo, Laravel has provided an out of the box solution for implementing realtime data synchronisation using event broadcasting. It is simple and we can get started in a matter of few minutes.
Live commenting allows users to see new comments from other users without reloading the page. This paves the way to have better collaboration and conversation between friends and collaborators. It brings dynamic feel to the application interface and a better usability.
A basic understanding of Laravel and Vue is needed to understand this tutorial.
We need to sign up on Pusher and create a new app.
First, we will grab a fresh copy of Laravel:
1laravel new live-commenting-pusher-laravel
This will install the latest version of the Laravel framework and download the necessary dependencies. Next, we will install the Pusher PHP SDK using Composer:
1composer require pusher/pusher-php-server
Next, we will install the JavaScript dependencies:
1npm install
Now, we need to install two Javascript libraries necessary for realtime event broadcasting: Laravel Echo and Pusher JS
1npm install --save laravel-echo pusher-js
We require some form of user authentication mechanism to demonstrate the functionality. Let us use the default authentication scaffolding provided by Laravel:
1php artisan make:auth
First, we need to set the APP_ID
, APP_KEY
, APP_SECRET
and APP_CLUSTER
in the environment file. We can get these details in our Pusher app dashboard:
1## .env 2 3BROADCAST_DRIVER=pusher 4 5PUSHER_APP_ID=your-pusher-app-id 6PUSHER_APP_KEY=your-pusher-app-key 7PUSHER_APP_SECRET=your-pusher-app-secret 8PUSHER_APP_CLUSTER=your-pusher-app-cluster
Next, we need to create a fresh Echo instance in our applications’s JavaScript. We can do so at the bottom of our resources/assets/js/bootstrap.js
file:
1import Echo from "laravel-echo" 2 3window.Echo = new Echo({ 4 broadcaster: 'pusher', 5 key: 'your-pusher-app-key', 6 cluster: 'ap2', 7 encrypted: true 8});
We will create a live commenting functionality for a video similar to what YouTube does. The core feature is the number of users active on this page and whenever a user comments on the video, it will be automatically broadcasted as a feed of the comments to all the viewers who are viewing this video. We will not cover anything relating to writing CRUD functionality using Laravel. We will concentrate on the code necessary for implementing the live commenting feature. The code is available on a Github repository for cloning and understanding purposes.
We will now show the number of users who are currently online on the video page. This is a typical feature where we show a message: 2 users watching now. Whenever a new user joins or leaves the page, the count is automatically updated. Let us implement that with Echo and it is super simple. Let us go and fill out our listen
method in the Example
Vue component.
1listen() { 2 Echo.join('video') 3 .here((users) => { 4 this.count = users.length; 5 }) 6 .joining((user) => { 7 this.count++; 8 }) 9 .leaving((user) => { 10 this.count--; 11 }); 12}
join
is used when we want to join a presence channel and that is used to tell us who else is on the page. Presence channels are automatically private channels. We do need to authenticate them.
here
gives us a list of users that are also present on the same page.
joining
will be executed whenever a new user joins the channel or page in the above scenario.
leaving
will be executed whenever a user leaves the channel.
Below is the demonstration where the count of users present on the page is updated automatically when a user leaves or joins the page.
Next, we need a comments
table where we can store all the comments for the video.
1php artisan make:model Comment -m
The comments
table would require the following fields:
Below is the migration for our comments
table:
1<?php 2 3use Illuminate\Support\Facades\Schema; 4use Illuminate\Database\Schema\Blueprint; 5use Illuminate\Database\Migrations\Migration; 6 7class CreateCommentsTable extends Migration 8{ 9 /** 10 * Run the migrations. 11 * 12 * @return void 13 */ 14 public function up() 15 { 16 Schema::create('comments', function (Blueprint $table) { 17 $table->increments('id'); 18 $table->text('body'); 19 $table->integer('video_id')->unsigned(); 20 $table->integer('user_id')->unsigned(); 21 $table->timestamps(); 22 }); 23 } 24 25 /** 26 * Reverse the migrations. 27 * 28 * @return void 29 */ 30 public function down() 31 { 32 Schema::dropIfExists('comments'); 33 } 34}
Whenever a new comment is created, we need to fire an event which will be broadcasted over Pusher to a specific presence channel. For broadcasting an event, it should implement the ShouldBroadcast
interface. Let us first create the NewComment
event:
1php artisan make:event NewComment
The event should implement a broadcastOn
method. This method should return the presence channel to which the event should be broadcasted.
1namespace App\Events; 2 3use App\Comment; 4use Illuminate\Broadcasting\Channel; 5use Illuminate\Broadcasting\InteractsWithSockets; 6use Illuminate\Broadcasting\PresenceChannel; 7use Illuminate\Broadcasting\PrivateChannel; 8use Illuminate\Contracts\Broadcasting\ShouldBroadcast; 9use Illuminate\Foundation\Events\Dispatchable; 10use Illuminate\Queue\SerializesModels; 11 12class NewComment implements ShouldBroadcast 13{ 14 use Dispatchable, InteractsWithSockets, SerializesModels; 15 16 public $comment; 17 18 public function __construct(Comment $comment) 19 { 20 $this->comment = $comment; 21 } 22 23 public function broadcastOn() 24 { 25 return new PresenceChannel('video'); 26 } 27 28 public function broadcastWith() 29 { 30 return [ 31 'id' => $this->comment->id, 32 'body' => $this->comment->body, 33 'user' => [ 34 'name' => $this->comment->user->name, 35 'id' => $this->comment->user->id, 36 ], 37 ]; 38 } 39}
Next, we need to start our queue to actually listen for jobs and broadcast any events that are recorded. We can use the database queue listener on our local environment:
1php artisan queue:listen
Whenever a new comment is created, we will broadcast the NewComment
event using the broadcast
helper:
1public function store(Video $video) 2{ 3 $comment = $video->comments()->create([ 4 'body' => request('body'), 5 'user_id' => auth()->user()->id, 6 'video_id' => $video->id 7 ]); 8 9 $comment = Comment::where('id', $comment->id)->with('user')->first(); 10 11 broadcast(new NewComment($comment))->toOthers(); 12 13 return $comment; 14}
Whenever we create a new comment, we push it to the comments
variable so that it is reflected instantly for the user. If we do not use the toOthers
method, then the event would also be broadcasted to the user who has created it. This would create a list of duplicate comments.
toOthers
allows you to exclude the current user from the broadcast’s recipients.
Installation and configuration of Laravel Echo is a must before we can start listening to new comments. We have covered the process in detail in the above section of this article. Please go through it if you might have skipped it.
1Echo.join('video') 2 .here((users) => { 3 this.count = users.length; 4 }) 5 .joining((user) => { 6 this.count++; 7 }) 8 .leaving((user) => { 9 this.count--; 10 }) 11 .listen('NewComment', (e) => { 12 this.comments.unshift(e); 13 });
Every presence channel is a private channel. Laravel Echo will automatically call the specified authentication route. But, we still need to write the authentication logic which will actually authorize the user to listen to a particular channel.
Authorization logic is written in the routes/channels.php
. The authorization logic for our video
channel is:
1Broadcast::channel('video', function ($user) { 2 return [ 3 'id' => $user->id, 4 'name' => $user->name, 5 ]; 6});
We are not going to return true
or false
. If the user is authenticated to listen on this presence channel we are going to return an array of data that we want to be returned to that callback in the listen
method.
You can write the authentication logic as required for your application in the above callback and return null if authorization fails.
That’s it! Now, whenever a new comment is created, it will be broadcast and we can listen using our presence channel.
Below is our Example component written using Vue.js
1<template> 2<div class="container"> 3 <div class="row"> 4 <div class="col-sm-5"> 5 <div class="panel panel-primary"> 6 <div class="panel-heading" id="accordion"> 7 <span class="glyphicon glyphicon-comment"></span> Chat 8 <div class="btn-group pull-right"> 9 <a type="button" class="btn btn-default btn-xs" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"> 10 <span class="glyphicon glyphicon-chevron-down"></span> 11 </a> 12 </div> 13 </div> 14 <div class="panel-collapse collapse" id="collapseOne"> 15 <div class="panel-body"> 16 <ul class="chat"> 17 <li class="left clearfix" v-for="comment in comments"> 18 <span class="chat-img pull-left"> 19 <img src="http://placehold.it/50/55C1E7/fff&text=U" alt="User Avatar" class="img-circle" /> 20 </span> 21 <div class="chat-body clearfix"> 22 <div class="header"> 23 <strong class="primary-font">{{ comment.user.name }}</strong> <small class="pull-right text-muted"> 24 <span class="glyphicon glyphicon-time"></span>12 mins ago</small> 25 </div> 26 <p> 27 {{ comment.body }} 28 </p> 29 </div> 30 </li> 31 </ul> 32 </div> 33 <div class="panel-footer"> 34 <div class="input-group"> 35 <input id="btn-input" type="text" class="form-control input-sm" placeholder="Type your message here..." v-model="body" @keyup.enter="postComment()" /> 36 <span class="input-group-btn"> 37 <button class="btn btn-warning btn-sm" id="btn-chat" @click.prevent="postComment()"> 38 Send</button> 39 </span> 40 </div> 41 </div> 42 </div> 43 </div> 44 </div> 45 <div class="col-sm-7"> 46 <iframe width="640" height="280" src="https://www.youtube.com/embed/Xip2TgAEVz4" frameborder="0" allowfullscreen></iframe> 47 </div> 48 </div> 49 <div class="row"> 50 <div class="col-sm-12"> 51 <h1> 52 {{ count }} watching now 53 </h1> 54 </div> 55 </div> 56</div> 57</template> 58 59<script> 60 export default { 61 props: ['user', 'video'], 62 data() { 63 return { 64 viewers: [], 65 comments: [], 66 body: 'Your comment', 67 count: 0 68 } 69 }, 70 mounted() { 71 this.listen(); 72 this.getComments(); 73 }, 74 methods: { 75 getComments() { 76 axios.get('/api/videos/'+ this.video.id +'/comments?api_token=' + this.user.api_token, {}) 77 .then((response) => { 78 this.comments = response.data; 79 }); 80 }, 81 postComment() { 82 axios.post('/api/videos/'+ this.video.id +'/comment?api_token=' + this.user.api_token, { 83 body: this.body 84 }) 85 .then((response) => { 86 this.comments.unshift(response.data); 87 }); 88 }, 89 listen() { 90 Echo.join('video') 91 .here((users) => { 92 this.count = users.length; 93 }) 94 .joining((user) => { 95 this.count++; 96 }) 97 .leaving((user) => { 98 this.count--; 99 }) 100 .listen('NewComment', (e) => { 101 this.comments.unshift(e); 102 }); 103 } 104 } 105 } 106</script>
Below is the image demonstrating the workflow of our system:
In this article, we have covered how to create a live commenting system using Laravel and Pusher. We have covered the configuration options necessary to get started, and the example above should help you fill in the gaps and give an overview of some of the other configuration options available to you.
The code is hosted on public Github repository. You can download it for educational purposes. How do you intend to use Laravel and Pusher? Can you think of any advanced use cases? What are they? Let us know in the comments!