Your Guide to Laravel Excellence

How to Add Real-Time Comments in Laravel 11 with Laravel Reverb

How to Add Real-Time Comments in Laravel 11 with Laravel Reverb

In this guide, we will create a real-time commenting system for a Laravel 11 application. This system will enable users to see and post comments instantly, without needing to refresh the page. By integrating Laravel Echo, Laravel Reverb, and Pusher, we will ensure that comment updates are broadcasted in real time.

What We Will Do

  • Install Laravel 11: set up a new Laravel project.

  • Set Up Authentication: Use Laravel UI to scaffold authentication.

  • Create the Comment System: Implement controllers, routes, and the necessary logic for handling comments.

  • Install and Configure Laravel Reverb: Set up Laravel Reverb for real-time broadcasting.

  • Set up Broadcasting: Configure broadcasting with events and channels.

  • Install and Configure laravel-echo pusher-js: Set up Pusher as an alternative or additional broadcasting driver.

  • Create Blade Templates: Design the front-end to display and submit comments.

  • Configure JavaScript for Echo: Handle real-time updates on the client side.

1. Installing Laravel 11

To start, we need to install Laravel 11. Use Composer to create a new Laravel project by using the following code:

composer create-project --prefer-dist laravel/laravel laravel-realtime-comments

Navigate to the project directory by using the following code:

cd laravel-realtime-comments

2. Setting Up Authentication with Laravel UI

Authentication is animportant part of any application, and Laravel UI makes it easy to set up. We'll use Laravel UI to generate the necessary views and routes for user authentication.

Install Laravel UI by using the following code:

composer require laravel/ui

php artisan ui bootstrap --auth

npm install && npm run dev

With authentication set up, users can now register, log in, and log out, which is necessary for identifying who is posting the comments.

3. Creating the Comment System

Comment Controller

We need to create a controller to handle the storage and retrieval of comments. This controller will manage the logic for saving comments to the database and broadcasting them in real-time. To create the comment controller use the following code.

php artisan make:controller CommentController

Edit app/Http/Controllers/CommentController.php to include:

Edit app/Http/Controllers/CommentController.php to include the following methods:

<?php
namespace App\Http\Controllers;

use App\Events\CommentPosted;
use App\Models\Post;
use Illuminate\Http\Request;

class CommentController extends Controller
{
    public function store(Request $request, Post $post)
    {
        $validated = $request->validate([
            'comment' => 'required|string|max:255',
        ]);

        $validated['user_id'] = auth()->user()->id;
        $comment = $post->comments()->create($validated);
        broadcast(new CommentPosted($comment))->toOthers();
        return response()->json([
            'status' => 'success',
            'message' => 'Comment posted successfully.',
            'comment' => $comment,
        ], 200);
    }

    public function index(Post $post)
    {
        $comments = $post->comments()->latest()->get();
        return response()->json($comments);
    }
}

This comment controller allows us to handle all aspects of comment management, from storing new comments to retrieving existing ones.

3. Routes

We need to define routes to handle comment-related actions. These routes will connect our controller methods to specific URLs in the application.

Add the following routes in routes/web.php:

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/posts/{post}/comments', [CommentController::class, 'index'])->name('comments.index');
Route::post('/posts/{post}/comments', [CommentController::class, 'store'])->name('comments.store');

Auth::routes();

These routes allow users to fetch and post comments on specific posts, which is the basis for our real-time commenting system.

4. Installing and Configuring Laravel Reverb

Laravel Reverb is a broadcasting driver designed for real-time communication in Laravel applications. It allows users to receive updates instantly as they occur. We’ll install Reverb along with Laravel Echo, which will be used on the client side to listen for broadcasted events.

Install Laravel Echo and Reverb by using the following code:

php artisan install:broadcasting

.env Configuration

we need to configure the .env file to include Reverb settings. This configuration will tell Laravel to use Reverb for broadcasting events.

Adjust the following code to your .env file:

BROADCAST_CONNECTION=reverb

REVERB_APP_ID=151212
REVERB_APP_KEY=1rcztnsuiavyzgsomsms
REVERB_APP_SECRET=uezrgv668uyajdrlq69x
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

5. Setting Up Broadcasting

Broadcasting events is at the core of any real-time system. We’ll create an event that will be broadcasted whenever a comment is posted.

Generate the CommentPosted event by using the following code:

php artisan make:event CommentPosted

Inside the generated event class, include the necessary properties and the broadcastOn method to define the channel the event will broadcast on. Here’s an example:

<?php
namespace App\Events;

use App\Models\Comment;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class CommentPosted  implements ShouldBroadcastNow
{

    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $comment;
    public function __construct(Comment $comment)
    {
        $this->comment = $comment;
    }

    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("posts.{$this->comment->post_id}"),
        ];
    }

    public function broadcastAs()
    {
        return 'CommentPosted';
    }

    /**
     * The data to broadcast with the event.
     *
     * @return array
     */

    public function broadcastWith()
    {
        return [
            'id' => $this->comment->id,
            'user_id' => $this->comment->user_id,
            'comment' => $this->comment->comment,
            'created_at' => $this->comment->created_at,
        ];
    }
}

Channels Configuration

To make sure that events are only broadcast to the right users, we’ll configure the broadcast channels in routes/channels.php.

Add the following channel configuration:

Broadcast::channel('posts.{id}', function ($user) {
    return true;
});

6. Installing and Configuring Pusher

Pusher is a popular service for managing broadcasting events. While Reverb handles broadcasting within your Laravel application, Pusher can be used as an alternative or additional broadcasting driver.

Install Pusher using npm:

npm install --save laravel-echo pusher-js

7. Blade Template for Comments

We will create a Blade template that displays comments and includes a form for submitting new comments. This template will be responsible for rendering the front-end of our real-time commenting system.

Create a comments.blade.php file in the resources/views directory and add the following content:

This simple template lists all comments and provides a form for adding new ones. When a user submits the form, the comment will be posted in real-time, thanks to the broadcasting we’ve set up.

<extends>('layouts.app')
@section('content')

    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="container">
                    <h1>Title: {{ $post->title }}</h1>
                    <p>Body: {{ $post->body }}</p>
                    <div id="comments-section" data-comment-section data-post-id="{{ $post->id }}"
                        data-comment-list="comments-list" data-comment-form="comment-form">
                        <div class="card mb-4">
                            <div class="card-header">
                                <h5>Comments</h5>
                            </div>

                            <div class="card-body" class="mb-5">
                                <ul class="list-group"  id="comments-list">
                                    <!-- Comment 1 -->
                                </ul>
                            </div>
                        </div>
                        <div>

                            <!-- Comments will be dynamically loaded here -->

                        </div>
                        <form id="comment-form" method="POST" action="{{ route('comments.store', $post) }}">
                            @csrf
                            <div class="mb-3">
                                <label for="comment-body" class="form-label">Your Comment</label>
                                <textarea class="form-control" name="comment" id="comment-body" rows="3" placeholder="Enter your comment"></textarea>
                            </div>
                            <button type="submit" class="btn btn-primary">Submit Comment</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

8. JavaScript Configuration for Echo

We will configure the JavaScript to listen for new comments using Laravel Echo. This will make sure that whenever a comment is posted, it appears in real-time for all users viewing the post.

Add the following JavaScript to your resources/js/app.js file

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});


class CommentSection {

    constructor(postId, commentListId, commentFormId) {
        this.postId = postId;
        this.commentList = document.getElementById(commentListId);
        this.commentForm = document.getElementById(commentFormId);
        this.commentInput = this.commentForm.querySelector('textarea');
        this.setupEventListeners();
        this.fetchComments();
        this.listenForNewComments();
    }

    setupEventListeners() {
        this.commentForm.addEventListener('submit', (e) => {
            e.preventDefault();
            this.addComment();
        });
    }

    async fetchComments() {
        try {
            // console.log("Fetching comments for post:", this.postId);
            const response = await fetch(`/posts/${this.postId}/comments`);
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }

            const comments = await response.json();
            this.renderComments(comments);
            // console.log("Comments fetched and rendered");
        } catch (error) {
            console.error("Error fetching comments:", error);
        }
    }

    renderComments(comments) {
        this.commentList.innerHTML = comments.map(comment => this.createCommentHTML(comment)).join('');
    }

    createCommentHTML(comment) {
        return `
                
                    Posted by User ${comment.user_id} - ${new Date(comment.created_at).toLocaleString()}
                    ${comment.comment}
                
        `;
    }

    async addComment() {
        const content = this.commentInput.value.trim();
        if (!content) return;
        try {

            // console.log("Adding comment:", content);
            const response = await fetch(`/posts/${this.postId}/comments`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
                },
                body: JSON.stringify({ comment: content }),
            });

            if (response.ok) {
                this.commentInput.value = '';
                // console.log("Comment added successfully");

            } else {
                throw new Error('Network response was not ok');
            }
        } catch (error) {
            console.error("Error adding comment:", error);
        }
    }

    listenForNewComments() {
        window.Echo.private(`posts.${this.postId}`)
            .listen('.CommentPosted', (e) => {
                console.log("New comment received:", e);
                const commentHTML = this.createCommentHTML(e);
                this.commentList.insertAdjacentHTML('beforeend', commentHTML);
            });
    }

}

// Initialize comment sections when the DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
    const commentSections = document.querySelectorAll('[data-comment-section]');
    commentSections.forEach(section => {
        const postId = section.dataset.postId;
        const listId = section.dataset.commentList;
        const formId = section.dataset.commentForm;
        new CommentSection(postId, listId, formId);
    });
});

Steps:

  • Configure Laravel Echo: Set up Laravel Echo with a broadcasting service like Reverb or Pusher to handle real-time events.

  • Initialize DOM: On page load, identify all comment sections and initialize the CommentSection class for each.

  • Create CommentSection Class:

  • Constructor: Initializes the class with the necessary IDs and sets up event listeners.

  • addComment: Handles adding a new comment via AJAX, posting it to the server.

  • fetchComments: Retrieves existing comments from the server and displays them.

  • listenForNewComments: Uses Echo to listen for new comment events in real-time and updates the UI accordingly.

Recommeded Posts

Laravel Tip: The "whereKey" Method

Laravel Tip: The "whereKey" Method

Laravel Tip: The "whereKey" Method

1 month ago Read article →
How to add Yajra DataTables in Laravel Step-by-Step Guide

How to add Yajra DataTables in Laravel Step-by-Step Guide

how to add Yajra DataTables in Laravel Step-by-Step Guide

1 month ago Read article →
Resize Images using intervention-image

Resize Images using intervention-image

Resize Images using intervention-image

1 month ago Read article →
Upload Huge File - Mastering Chunked File Uploads in Laravel

Upload Huge File - Mastering Chunked File Uploads in Laravel

Upload Huge File - Mastering Chunked File Uploads in Laravel

1 month ago Read article →