Introduction
You'll gain insights into the significance of real-time communication by creating a chat app in laravel using laravel websockets and pusher.We will deep dive into it and guide each and everything in detail.We will start form beginner and go advance, so this tutorial is for both for beginner and advance.
We will have 4 section for this tutorial.
Section 1: Sending Messages via Websockets and Broadcasting Events
Section 2: Receiving Messages using Pusher
Section 3: Displaying User Status (Online/Offline)
Section 4: Displaying Old Messages of Every User
Section 1: Sending Messages via Websockets and Broadcasting Events
In this section, we'll talk about the process of sending messages through Websockets in Laravel. You'll learn how to set up Laravel Websockets, create event listeners, and broadcast events to clients in real-time.
Setting Up Laravel
- Start by creating a fresh project.
- Use your .env and connect db
- We will also use default users table
We need to create a table where we store chats .Let’s create it.
php artisan make:model Chat -m
Our chats table.
Schema::create('chats', function (Blueprint $table) {
$table->id();
$table->foreignId('sender_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('receiver_id')->constrained('users')->cascadeOnDelete();
$table->string('message');
$table->timestamps();
});
Our Chat Model
class Chat extends Model
{
use HasFactory;
protected $fillable = [
'sender_id', 'receiver_id', 'message'
];
}
Configure Laravel Websockets
Install it by using composer
composer require beyondcode/laravel-websockets
If you want to see statistics and information you can publish a migration file.
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
Now migrate it
php artisan migrate
Now publish configuration
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
Now inside config/websockets.php
'apps' => [
[
'id' => env('PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'path' => env('PUSHER_APP_PATH'),
'capacity' => null,
'enable_client_messages' => false,
'enable_statistics' => true,
],
],
.env file
PUSHER_APP_ID=local
PUSHER_APP_KEY=local
PUSHER_APP_SECRET=local
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
User Authentication & User Interface
For authentication we will use breeze , you can install any package or make custom authentication.
composer require laravel/breeze --dev
php artisan breeze:install
php artisan migrate
npm install
npm run dev
Register some user so that we can show a list of users in our chat app.
Create a controller
php artisan make:Controller MessageController
Here we define our complete logic.Navigate it to this File
public function index(Request $request)
{
$users = User::whereNotIn('id', [auth()->user()->id])->get();
return view("dashboard", compact("users"));
}
Here's our route
// Route::get('/dashboard', function () {
// return view('dashboard');
// })->middleware(['auth', 'verified'])->name('dashboard');
Route::get('/dashboard', [MessageController::class, 'index'])->middleware(['auth', 'verified'])->name('dashboard');
We need to add out frontend chatapp code and show a list of users So Navigate to views\dashboard.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ "Dashboard" }}
</h2>
</x-slot>
<div class="py-12">
<div id="hello-message">
<!-- The received hello message will be displayed here -->
</div>
<div class="container mx-auto shadow-lg rounded-lg">
<!-- headaer -->
<div class="px-5 py-5 flex justify-between items-center bg-white border-b-2">
<div class="font-semibold text-2xl">Laravel ChatApp</div>
<div
class="h-12 w-12 p-2 bg-yellow-500 rounded-full text-white font-semibold flex items-center justify-center">
RA
</div>
</div>
<!-- end header -->
<!-- Chatting -->
<div class="flex flex-row justify-between bg-white" style="height: 60vh;">
<!-- chat list -->
<div class="flex flex-col w-2/5 border-r-2 overflow-y-auto">
<!-- search compt -->
<div class="border-b-2 py-4 px-2">
<input type="text" placeholder="search chatting"
class="py-2 px-2 border-2 border-gray-200 rounded-2xl w-full" />
</div>
<!-- user list -->
@foreach ($users as $user)
<div class="user_list flex flex-row py-4 px-2 justify-center items-center border-b-2"
data-id="{{ $user->id }}">
<div class="w-1/4">
<img src="https://source.unsplash.com/_7LbC5J-jw4/600x600"
class="object-cover h-12 w-12 rounded-full" alt="" />
</div>
<div class="w-full">
<div class="text-lg font-semibold">{{ $user->name }}</div>
<span class="text-gray-500 lastest__message">
</span>
<span class="text-white bg-sky-300 rounded-full px-2 py-1"
id="{{ $user->id }}-status">offline</span>
</div>
</div>
@endforeach
<!-- end user list -->
</div>
<!-- end chat list -->
<!-- message -->
<div class="w-full flex flex-col justify-between chatbox" style="display: none;">
<div class="px-5 overflow-y-auto chat-overflow">
<div class="flex flex-col mt-5 chat-div">
<!-- <div class="flex justify-end mb-4">
<div id="text-message"
class="mr-2 py-3 px-4 bg-blue-400 rounded-bl-3xl rounded-tl-3xl rounded-tr-xl text-white">
Welcome to group everyone !</div>
<img src="https://source.unsplash.com/vpOeXr5wmR4/600x600"
class="object-cover h-8 w-8 rounded-full" alt="" />
</div>
<div class="flex justify-start mb-4">
<img src="https://source.unsplash.com/vpOeXr5wmR4/600x600"
class="object-cover h-8 w-8 rounded-full" alt="" />
<div
class="ml-2 py-3 px-4 bg-gray-400 rounded-br-3xl rounded-tr-3xl rounded-tl-xl text-white">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat
at praesentium, aut ullam delectus odio error sit rem. Architecto
nulla doloribus laborum illo rem enim dolor odio saepe,
consequatur quas?</div>
</div> -->
</div>
</div>
<div class="py-5">
<form id="message-form" class="flex">
<input class="bg-gray-300 w-5/6 py-5 px-3" type="text" name="message" id="message"
placeholder="type your message here..." />
<button class="w-1/6 bg-sky-400" id="submit_btn">Send</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
Send Message
Define sender_id and receiver_id globally.
var sender_id = @json(auth()->user()->id);
var receiver_id;
Now let's send a message using ajax request , we will create an event and broadcast the message.
// JQuery
$(document).ready(function() {
//Users List
$('.user_list').click(function() {
var getUserId = $(this).attr('data-id');
receiver_id = getUserId;
console.log(receiver_id);
$('.user_list').removeClass('active');
$(this).addClass('active');
$('.chatbox').show();
loadOldChats();
scrollChat();
});
// Send Message Through Ajax
$('#submit_btn').on('click', function(event) {
event.preventDefault();
const formData = new FormData(document.getElementById("message-form"));
formData.append('receiver_id', receiver_id);
formData.append('sender_id', sender_id);
$.ajax({
url: "{{ route('message.save') }}",
data: formData,
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
processData: false,
contentType: false,
cache: false,
}).then(function(response) {
console.log(response);
$('#message').val('');
loadOldChats();
scrollChat();
})
.fail(function(response) {
console.log(response);
});
});
});
Our Route for this request
Route::post('/save-chat', [MessageController::class, 'sendMessage'])->name('message.save');
No go inside and MessageControllercreate **sendMessage** function
public function sendMessage(Request $request)
{
$request->validate([
'receiver_id' => 'required',
'sender_id' => 'required',
'message' => 'required|string',
]);
try {
$chat = Chat::create([
'receiver_id' => $request->receiver_id,
'sender_id' => $request->sender_id,
'message' => $request->message,
]);
broadcast(new MessageSent($chat));
return response()->json([
"success" => true,
"data" => $chat,
]);
} catch (\Exception $e) {
return response()->json([
"success" => false,
"msg" => $e->getMessage()
]);
}
}
We need to create Event , which we will broadcast.
php artisan make:event MessageSent
Now go inside App\Events\MessageSent
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public $chat;
public function __construct($chat)
{
$this->chat = $chat;
}
/**
* Get the channels the event should broadcast on.
*
* @return array
*/
public function broadcastOn(): Channel
{
return new PrivateChannel('messageChannel');
}
public function broadcastWith()
{
return [
'chat' => $this->chat
];
}
public function broadcastAs()
{
return 'getChatMessage';
}
}
Now we need to define route for this event that will check if the user is authenticated or not. So navigate it to **routes\channels.php**
Broadcast::channel('messageChannel', function ($user) {
return $user;
});
Section 2: Receiving Messages using Pusher
We'll explore how to receive messages using Pusher, a powerful real-time messaging service.
composer require pusher/pusher-php-server
Goto your resources\js\bootstrap.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true,
});
Now let's listen for and event. You can use your custom js file or you can use blade file to add js.
//Listen for an Event
window.Echo.private('messageChannel').listen('.getChatMessage', (e) => {
let ChatMessage = e.chat.message;
let senderId = e.chat.sender_id;
let recevierId = e.chat.receiver_id;
if (sender_id == recevierId && receiver_id == senderId) {
$('.chat-div').append(`
`);
scrollChat();
}
});
Section 3:Displaying User Status (Online/Offline)
We'll focus on enhancing the user experience by displaying user status indicators, indicating whether they are online or offline.We need to create another event for this but this we will use presence channel instead of public to show user online/offline status.
php artisan make:event UserChatStatus
Navigate to this event
public function broadcastOn(): array
{
return [
new PresenceChannel('userChatStatusUpdate'),
];
}
Now define a event broadcasting channel (routes\channels.php)
Broadcast::channel('userChatStatusUpdate', function ($user) {
return $user;
});
Here we are listening for an userChatStatusUpdate Event and updating the status at real-time.
//Presence Channel (Check User is Online or Offline)
window.Echo.join('userChatStatusUpdate')
.here((users) => {
console.log(users);
for (let i = 0; i < users.length; i++) {
if (sender_id != users[i]['id']) {
$('#' + users[i]['id'] + '-status').removeClass('bg-sky-300');
$('#' + users[i]['id'] + '-status').addClass('bg-lime-600');
$('#' + users[i]['id'] + '-status').text('Online');
}
}
})
.joining((user) => {
console.log(user.name);
$('#' + user.id + '-status').removeClass('bg-sky-300');
$('#' + user.id + '-status').addClass('bg-lime-600');
$('#' + user.id + '-status').text('Online');
})
.leaving((user) => {
console.log(user.name);
$('#' + user.id + '-status').removeClass('bg-lime-600');
$('#' + user.id + '-status').addClass('bg-sky-300');
$('#' + user.id + '-status').text('Offline');
})
.error((error) => {
console.error(error);
});
Section 4 : Show old Message of Every User
Navigate to your MessageController and create a method named show().Which will retrieve all the users messages.
public function show(Request $request)
{
try {
$chat = Chat::where(function ($q) use ($request) {
$q->where('sender_id', '=', $request->receiver_id)
->orwhere('sender_id', '=', $request->sender_id);
})->where(function ($q) use ($request) {
$q->where('receiver_id', '=', $request->receiver_id)
->orwhere('receiver_id', '=', $request->sender_id);
})->get();
return response()->json([
"success" => true,
"data" => $chat,
]);
} catch (\Exception $e) {
return response()->json([
"success" => false,
"msg" => $e->getMessage()
]);
}
}
Here we are getting all the messages and fixing scroll issue so that we see latest message everytime.
// JQuery
$(document).ready(function() {
scrollChat();
// Scroll
function scrollChat() {
$('.chat-overflow').animate({
scrollTop: $('.chat-overflow').offset().top + $('.chat-overflow')[0].scrollHeight
}, 0);
}
// Showing OldChats
function loadOldChats() {
$.ajax({
url: '{{ route('message.old.show') }}',
type: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: {
sender_id: sender_id,
receiver_id: receiver_id,
},
success: function(response) {
$('.chat-div').empty();
if (response.success) {
let chats = response.data;
if (response.success) {
for (let i = 0; i < chats.length; i++) {
$('.chat-div').append(`
`);
}
}
}
scrollChat();
},
error: function(xhr, textStatus, errorThrown) {
console.error(xhr.responseText);
}
});
}
});
Our All Routes of web.php
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', [MessageController::class, 'index'])->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit')
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::post('/save-chat', [MessageController::class, 'sendMessage'])->name('message.save');
Route::post('/show', [MessageController::class, 'show'])->name('message.old.show');
});
require __DIR__ . '/auth.php';