How to Make a Live Chat Using Pusher in Laravel?

By Shubham G on Apr 11, 2025

In modern web applications, real-time features have become essential, whether it's notifications, updates, or live chat systems. Laravel, coupled with Pusher, makes it incredibly simple to implement a robust live chat system. In this post, we'll guide you through the steps to create a live chat using Pusher in Laravel.


Preview : 

Step 1 : Pusher Credentials

To obtain the Pusher credentials, either login or sign-up at https://dashboard.pusher.com/. Once logged in, you'll find an option to create a channel app. Create the app tailored to your specific requirements. 


Once you've created the app, open its details. On the left-hand side, you'll find the 'App Keys' section. Copy these keys and store them securely, ensuring they are not exposed to the public.


Step 2 : Setting up pusher in laravel

We will utilize the Pusher package. To install it, execute the command given below.

composer require pusher/pusher-php-server

Open the '.env' file and replace the environment variables with the actual values. Ensure that the 'BROADCAST_DRIVER' is updated to 'pusher' as forgetting this step is a common mistake.

BROADCAST_DRIVER='pusher'




PUSHER_APP_ID='YOUR_APP_ID'
PUSHER_APP_KEY='YOUR_APP_KEY'
PUSHER_APP_SECRET='YOUR_APP_SECRET_KEY'
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER='YOUR_APP_CLUSTER'

Step 3 : Managing live chat database

With the Pusher setup in Laravel completed, we will now create a 'chats' table. To generate the model and migrations in one step, execute the command below.

php artisan make:model LiveChat -m

Once you execute this command, a schema/migration file will be generated in 'database/migrations/**********_create_live_chats_table.php'. Modify the file using the code provided below.

Schema::create('live_chats', function (Blueprint $table) {
    $table->id();
    $table->integer('send_by');
    $table->integer('send_to')->nullable();
    $table->text('message');
    $table->tinyInteger('status')->default(0);
    $table->string('deleted_by')->default(0);
    $table->timestamps();
});

After updating the schema/migration file, you must execute the migration command to create the table in your database. Use the command provided below to accomplish this.

php artisan migrate

Step 4 : Controller, Routes & Design

Let's create a controller to handle live chat functionality. Use the following command to generate the controller.

php artisan make:controller LiveChatController

Open web.php and add this routes

use App\Http\Controllers\LiveChatController;




// Live Chat Routes
Route::get('chat', [LiveChatController::class, 'index'])->name('chat.index');
Route::get('load-chat-user', [LiveChatController::class, 'loadUsers'])->name('load.chat.user');


Route::post('ajax/send-chat-message', [LiveChatController::class, 'sendMessage'])->name('send.chat.message');
Route::get('ajax/load-chat-messages', [LiveChatController::class, 'loadMessages'])->name('load.chat.messages');
Route::get('delete-chat/{id}', [LiveChatController::class, 'destroy'])->name('chat.destroy');

After adding the routes, navigate to 'LiveChatController.php'. Create a function called 'index' and replace its content with the provided code.

use App\Models\LiveChat;
use App\Models\User;
use Illuminate\Http\Request;



public function index(Request $request)
{
    $chat_id = $request->chat_id;
    $auth_id = auth()->id();
    $chats = [];
    $activeUser = [];
    if(!empty($chat_id))
    {
        $chatUser = [$chat_id,$auth_id];
        $baseChats = LiveChat::whereRaw('FIND_IN_SET(?, deleted_by) = 0', [$auth_id])->whereIn('send_by',$chatUser)->whereIn('send_to',$chatUser);
        $baseChats->update(['status' => 1]);
        
        $chats = $baseChats->orderBy('created_at')
            ->get()->groupBy(function($data) {
                return $data->created_at->format('d M Y');
            })->toArray();

        $activeUser = User::find($chat_id);
    }
    return view('live-chats.index',compact('auth_id','activeUser','chats','chat_id'));
}

Create a Blade file at 'resources/views/live-chats/index.blade.php'. Start by adding the HTML and CSS, and then proceed to work on the script and controller.

@extends('layouts.app')
@section('content')
<style>
    .cursor-pointer
    {
        cursor: pointer;
    }
    .there-message
    {
        background-color: #e5e4e4;
        color: black;
        padding: 10px;
        border-radius: 10px;
        margin-bottom: 20px;
        width: 50%;
    }
    .my-message
    {
        background-color: #657be4;
        color: white;
        padding: 10px;
        border-radius: 10px;
        margin-bottom: 20px;
        width: 50%;
        margin-left: auto;
    }
    .message-date
    {
        text-align: center;
        padding-bottom: 30px;
    }
    ul
    {
        list-style: none;
        padding-left: 0;
    }
    #chat-body
    {
        height: 500px;
        overflow: auto;
    }
    a{
        text-decoration: none;
        color: black;
    }
    .badge-primary{
        background-color: #ffffff;
        color: #657be4;
        font-size: 14px;
        border-radius: 25px;
        padding-left: 10px;
        padding-right: 10px;
        padding-top: 5px;
        padding-bottom: 5px;
    }
    .active-chat-user
    {
        background-color: #657be4;
    }
    .active-chat-user p{
        color: white;
    }
    .active-chat-user small{
        color: white;
    }
</style>
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-4">
            <ul class="list-group" id="user-list"></ul>
        </div>
        <div class="col-md-8">
            <div class="card">
                @if(!empty($chat_id))
                    <div class="card-header">
                        {{ $activeUser->name }}
                        <a href="{{ route('chat.destroy',$chat_id) }}" onclick="return confirm('Are you sure, This action cannot be undone!')" class="btn btn-danger btn-sm float-end">Delete</a>

                    </div>
                    <div class="card-body" id="chat-body">
                        <ul>
                            @if(count($chats) > 0)
                                @foreach($chats as $date => $chat)
                                    <li class="message-date">
                                        <span class="badge bg-secondary">{{ \Carbon\Carbon::parse($date)->isToday() ? 'Today' : $date }}</span>
                                    </li>
                                    @foreach($chat as $ch)
                                        @if($ch['send_by'] == $auth_id)
                                            <li class="my-message">
                                                {!! nl2br($ch['message']) !!}
                                            </li>
                                        @else
                                            <li class="there-message">
                                                {!! nl2br($ch['message']) !!}
                                            </li>
                                        @endif
                                    @endforeach
                                @endforeach
                            @else
                                <li class="message-date">
                                    <span class="badge bg-secondary">No Chats</span>
                                </li>
                            @endif
                        </ul>
                    </div>
                    <div class="card-footer text-end">
                        <div class="input-group">
                            <input type="text" id="message-inp" class="form-control" placeholder="Type your message here..." aria-label="Message" aria-describedby="send-btn">
                            <div class="input-group-append">
                                <span class="input-group-text cursor-pointer" id="send-btn">Send</span>
                            </div>
                        </div>
                    </div>
                @else
                    <div class="card-body" id="chat-body">
                        <p class="text-center pt-4">Please select a user to start chat!</p>
                    </div>
                @endif
            </div>
        </div>
    </div>
</div>
@endsection

Before proceeding to the script, add a small piece of code in 'app/Http/Middleware/VerifyCsrfToken.php' to eliminate the need for passing the CSRF token in the AJAX route.

protected $except = [
    '*/ajax/*'
];

Now, let's proceed to the script. Add these two JavaScript CDNs just before the closing body tag.

// Jquery CDN
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
// PUSHER CDN
<script src="https://js.pusher.com/7.0/pusher.min.js"></script>

After including the CDNs, add these two functions to manage the user list and load messages.

// to load user list
function loadUsers()
{
    $('#user-list').load('{{ route('load.chat.user',['chat_id' => $chat_id]) }}');
}


// to load messages
function loadMessages()
{
    $('#chat-body').load('{{ route('load.chat.messages',['chat_id' => $chat_id]) }}');
    scrollDown();
}

Include this to load the user list and scroll down to the chat either when the page is ready or upon receiving a Pusher request.

$(document).ready(function ()
{
    loadUsers();
    scrollDown();
});

var pusher = new Pusher('{{env('PUSHER_APP_KEY')}}', {
    cluster: '{{env('PUSHER_APP_CLUSTER')}}'
});
var channel = pusher.subscribe('live-chat-channel');
channel.bind('message-received', function(data) {
    if(data.send_by != '{{ $auth_id }}'){
        loadMessages();
    }
    loadUsers();
});

function scrollDown(time = 1500)
{
    var content = $('#chat-body');
    content.finish().animate({
        scrollTop: content.prop("scrollHeight")
    }, time);
}

Here's the final script for sending messages. It handles message transmission, loads the response, and scrolls down to the chat seamlessly.

$(document).on('click','#send-btn',function(e)
{
    var message = $('#message-inp').val();
    if(message != '')
    {
        $.ajax({
            type: 'POST',
            url: '{{ route('send.chat.message') }}',
            data: {
                message: message,
                chat_id: '{{ $chat_id }}',
            },
            success: function (response)
            {
                if (response.status == 'success')
                {
                    $('#message-inp').val('');
                    $("#chat-body").html('');
                    $("#chat-body").html(response.output);
                    scrollDown();
                }
                else
                {
                    console.log('Error : ' + response.message);
                }
            },
            error: function (response)
            {
                console.log('Error : Something went wrong!');
            }
        });
    }
});

With the script complete, we can now shift our focus to the controller. However, before proceeding, we'll create an event. This event will be triggered after sending a message, allowing Pusher to listen to it and subsequently load the user list and messages.

php artisan make:event LiveChatEvent

Once the command is executed, an event will be generated in 'app/Events/LiveChatEvent.php'. Open the file, make the necessary updates, and ensure to implement 'ShouldBroadcast'.

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class LiveChatEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    
    public function __construct()
    {
        //
    }
    
    public function broadcastOn()
    {
        return new Channel('live-chat-channel');
    }

    public function broadcastWith()
    {
        return ['send_by' => auth()->id()];
    }

    public function broadcastAs()
    {
        return 'message-received';
    }
}

With the event created and configured, let's proceed to the controller. Open 'LiveChatController' and start by adding this function to load the user list.

use App\Models\User;


public function loadUsers(Request $request)
{
    $chat_id = !empty($request->chat_id) ? $request->chat_id : 0;
    $auth_id = auth()->id();
    $chats = LiveChat::where('send_by',$chat_id)->orWhere('send_to',$auth_id)->orderBy('id','DESC')->get()->pluck('send_by')->toArray();
    $user_ids = User::get()->pluck('id')->toArray();
    $chatUsers = array_unique(array_merge($chats,$user_ids));
    $impUsers = implode(',',$chatUsers);
    $users = User::where('id','!=',$auth_id)->whereIn('id',$chatUsers)->orderByRaw("FIELD(id, $impUsers)")->get();
    $output = view('live-chats.ajax.user-list',compact('chat_id','users'))->render();
    return $output;
}

This function creates a grouped list of users and returns an HTML string using the render view function. Next, add a Blade file at 'resources/views/live-chats/ajax/user-list.blade.php'.

@foreach($users as $user)
    <li class="list-group-item {{ $chat_id == $user->id ? 'active-chat-user' : '' }}">
        <a href="{{ route('chat.index',['chat_id' => $user->id]) }}">
            <div class="row">
                <div class="col-10">
                    <p>{{ $user->name }}</p>
                    <small>{{ $user->email }}</small>
                </div>
                <div class="col-2">
                    @if($user->getUnreadCount() > 0)
                        <span class="badge-primary">{{ $user->getUnreadCount() }}</span>
                    @endif
                </div>
            </div>
        </a>
    </li>
@endforeach

Next, return to 'LiveChatController' and include this function to load messages.

public function loadMessages(Request $request)
{
    $chat_id = $request->chat_id;
    $auth_id = auth()->id();
    $chatUser = [$chat_id,$auth_id];

    $chats = LiveChat::whereRaw('FIND_IN_SET(?, deleted_by) = 0', [$auth_id])
        ->whereIn('send_by',$chatUser)
        ->whereIn('send_to',$chatUser)
        ->orderBy('created_at')
        ->get()->groupBy(function($data) {
            return $data->created_at->format('d M Y');
        })->toArray();

    $output = view('live-chats.ajax.messages',compact('auth_id','chat_id','chats'))->render();

    return $output;
}

This function also returns a rendered view file. Therefore, create a new Blade file at 'resources/views/live-chats/ajax/messages.blade.php'.

<ul>
    @if(count($chats) > 0)
        @foreach($chats as $date => $chat)
            <li class="message-date">
                <span class="badge bg-secondary">{{ \Carbon\Carbon::parse($date)->isToday() ? 'Today' : $date }}</span>
            </li>
            @foreach($chat as $ch)
                @if($ch['send_by'] == $auth_id)
                    <li class="my-message">
                        {!! nl2br($ch['message']) !!}
                    </li>
                @else
                    <li class="there-message">
                        {!! nl2br($ch['message']) !!}
                    </li>
                @endif
            @endforeach
        @endforeach
    @else
        <li class="message-date">
            <span class="badge bg-secondary">No Chats</span>
        </li>
    @endif
</ul>

With the load messages function finalized, we can move on to the send message function. Open 'LiveChatController' and add this function. It inserts data into the database, retrieves rendered content from 'messages.blade.php', and triggers a 'LiveChatEvent.'

use App\Events\LiveChatEvent;



public function sendMessage(Request $request)
{
    $chat_id = $request->chat_id;
    $auth_id = auth()->id();
    $message = $request->message;
    if(!empty($chat_id) && !empty($message))
    {
        $chatUser = [$chat_id,$auth_id];

        $newMessage = new LiveChat();
        $newMessage->send_by = $auth_id;
        $newMessage->send_to = $chat_id;
        $newMessage->message = $message;
        $newMessage->status = 0;
        $newMessage->save();

        $chats = LiveChat::whereRaw('FIND_IN_SET(?, deleted_by) = 0', [$auth_id])
            ->whereIn('send_by',$chatUser)
            ->whereIn('send_to',$chatUser)
            ->orderBy('created_at')
            ->get()->groupBy(function($data) {
                return $data->created_at->format('d M Y');
            })->toArray();

        LiveChat::where('send_by',$chat_id)->where('send_to',$auth_id)->update(['status' => 1]);

        $output = view('live-chats.ajax.messages',compact('auth_id','chat_id','chats'))->render();

        event(new LiveChatEvent());

        return [
            'status' => 'success',
            'output' => $output
        ];
    }
}

Once the send message function is complete, you'll need to add a small function in the 'User.php' model. This function will retrieve the count of unread messages. To do so, open 'app/Models/User.php' and include this function.

public function getUnreadCount()
{
    $count = LiveChat::where('send_by',$this->id)->where('status',0)->count();
    return $count;
}

The live chat process is now fully implemented, and we've arrived at the final step of deleting a chat. Let's create a function for this purpose. Instead of permanently deleting the data, this function will mark the ID of the user who has deleted the message. As a result, messages will be hidden from the user who deleted them but remain visible to users who have not deleted them.

use Illuminate\Support\Facades\DB;


public function destroy($chat_id)
{
    $auth_id = auth()->id();
    $chatUser = [$chat_id,$auth_id];

    LiveChat::whereRaw('FIND_IN_SET(?, deleted_by) = 0', [$auth_id])
        ->whereIn('send_by',$chatUser)->whereIn('send_to',$chatUser)->update([
            'deleted_by' => DB::raw("CONCAT(deleted_by,',$auth_id')")
        ],$auth_id);

    return redirect()->back()->withInput()->with('success', __('Chat deleted'));
}
Loading...