Let’s add real-time updates and notifications to our chat app using Laravel WebSockets, the Pusher-free alternative.
This is the part you are waiting for, I feel ya, my friend. It’s the real-time chat app moment, Let’s make it happen.
Course content
How does Real-time chat work?
Let’s say chat room means WebSocket channel, to enter the room, we need to subscribe to that channel first. After subscription, We are ready to receive any message from the server to the Frontend, without any HTTP or AJAX Requests.
The sender will send the message via HTTP request to the server so we can save the message in the database then the server will broadcast the message to the chat channel to other users so they will receive the message. It’s so simple like that.
Laravel WebSockets
As most Laravel real-time tutorials trick you to use Pusher, Here I recommend using Laravel WebSockets for free with no limitations.
We can install Laravel-websockets package via composer and run the following commands
Note, If you want to learn more here is a tutorial: How To Use Laravel WebSockets And Laravel Echo With Vue 3 App Example.
composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
composer require pusher/pusher-php-server "~3.0"
We need to make our BROADCAST_DRIVER
to be pusher
in .env
file.
BROADCAST_DRIVER=pusher
Below is the pusher configuration that was modified for Laravel websockets in config/broadcasting.php
. This data is for Localhost and the default port of the Laravel WebSocket server is 6001.
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'encrypted' => true,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http'
],
],
In .env
file make sure to set your pusher app id, key, secret, and cluster.
PUSHER_APP_ID=local
PUSHER_APP_KEY=local
PUSHER_APP_SECRET=local
PUSHER_APP_CLUSTER=mt1
Now we are ready to test our WebSockets server.
php artisan websockets:serve
Let’s run the Laravel server and open the Laravel WebSockets dashboard http://127.0.0.1:8000/laravel-websockets
php artisan serve
Add this route to routes\api.php
Broadcast::routes(['middleware' => ['auth:sanctum']]);
Uncomment App\Providers\BroadcastServiceProvider::class,
in config\app.php
at providers
Message Sent Event
Let’s create a Laravel event for sending messages so we can broadcast the message to the other users who subscribed to the same chat channel for real-time updates.
php artisan make:event ChatMessageSent
<?php
namespace App\Events;
use App\Models\ChatMessages;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Http\Resources\MassageResource;
class ChatMessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public function __construct(MassageResource $message)
{
$this->message = $message;
}
public function broadcastWith(){
return ['message'=> $this->message];
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PresenceChannel('chat.'.$this->message->chat_id),
];
}
}
That’s the event we will trigger, so let’s explain, $message
this is the message that will be broadcasted on the Presence channel “chat.[chat_id]”.
Note, you can change the chat_id with a customized secret key to be more secure
To make sure that only these chat participants can subscribe to this channel so no one else can see the chat messages, Add the following routes in routes\channels.php
use App\Models\Chat;
//
Broadcast::channel('chat.{id}', function ($user, $id) {
$chat = Chat::find($id);
if($chat->isParticipant($user->id)){
return ['id' => $user->id, 'name' => $user->first_name];
}
});
Now our chat is secure, and let’s hope that the “Snowden” movie is not true.
Let’s trigger the event in sendTextMessage()
function in ChatController
use App\Events\ChatMessageSent;
class ChatController extends Controller
{
public function sendTextMessage(SendTextMessageRequest $request){
$chat = Chat::find($request->chat_id);
if($chat->isParticipant($request->user()->id)){
$message = ChatMessages::create([
'message' => $request->message,
'chat_id' => $request->chat_id,
'user_id' => $request->user()->id,
'data' => json_encode(['seenBy'=>[],'status'=>'sent']) //sent, delivered,seen
]);
$success = true;
$message = new MassageResource($message);
// broadcast the message to all users
broadcast(new ChatMessageSent($message));
foreach($chat->participants as $participant){
if($participant->id != $request->user()->id){
$participant->notify(new NewMessage($message));
}
}
return response()->json( [
"message"=> $message,
"success"=> $success
],200);
}else{
return response()->json([
'message' => 'not found'
], 404);
}
}
//
Message Status Event
Let’s create another event when the user sees the message
php artisan make:event ChatMessageStatus
<?php
namespace App\Events;
use App\Models\ChatMessages;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Http\Resources\MassageResource;
class ChatMessageStatus implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public $message;
public function __construct(MassageResource $message)
{
$this->message = $message;
}
public function broadcastWith(){
return ['message'=> $this->message];
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PresenceChannel('chat.'.$this->message->chat_id),
];
}
}
Nothing new, all the same as ChatMessageSent
event, and we broadcast on the same channel.
public function messageStatus(Request $request,ChatMessages $message){
if($message->chat->isParticipant($request->user()->id)){
$messageData = json_decode($message->data);
array_push($messageData->seenBy,$request->user()->id);
$messageData->seenBy = array_unique($messageData->seenBy);
if(count($message->chat->participants)-1 < count( $messageData->seenBy)){
$messageData->status = 'delivered';
}else{
$messageData->status = 'seen';
}
$message->data = json_encode($messageData);
$message->save();
$message = new MassageResource($message);
//triggering the event
broadcast(new ChatMessageStatus($message));
return response()->json([
'message' => $message,
'success' => true
], 200);
}else{
return response()->json([
'message' => 'Not found',
'success' => false
], 404);
}
}
Congratulations, we have just finished our backend. Let’s create our frontend in the next tutorial Vue.js Chat App Frontend | Chat App 04