Laravel with Redis Pub/Sub: A Deep Dive into Real-Time Event Handling
In the ever-evolving realm of web applications, real-time features have become an integral part of user experiences. Whether it's live notifications, chat applications, or dynamic updates, the demand for seamless real-time interactions has never been higher. For developers, meeting these demands can be both a challenge and a thrill.
If you're a Laravel enthusiast, you're in luck. Laravel, one of the most popular PHP frameworks, comes equipped with powerful tools to bring real-time magic to your applications. In this blog, we dive into one of these magical tools: Redis Pub/Sub.
Introduction to Redis Pub/Sub
Redis Pub/Sub, short for Publish/Subscribe, is a robust messaging system built on top of Redis, the lightning-fast, open-source, in-memory data structure store. At its core, Redis Pub/Sub allows different components or parts of an application to communicate asynchronously by broadcasting and receiving messages. It follows a one-to-many communication model, where publishers send messages to channels, and subscribers who are listening to the channel, receive messages from those channels.Publishers and subscribers are decoupled, meaning they don’t have direct knowledge of each other.
Setting up Laravel with Redis
Before diving into the implementation, make sure you have Laravel installed and a Redis server running. You can install the predis/predis
package to simplify Redis integration: composer require predis/predis
.
Next, configure your Laravel application to use Redis by updating the broadcasting.php
configuration file. Set the driver
option to redis
and configure the connections
array.
Implementing Redis Pub/Sub in Laravel
For simplicity, let's build a real-time drawing playground as displayed above, where a node is subscribed to a public channel and another node is publishing drawing information (e.g. color code, pen width, x & y coordinates etc). You may refer to my CodePen for the UI.
Publishing to Redis Channels
In Laravel, channels serve as communication pathways. The following function fetches the request body, publishes the data to
public.playground.1
channel withRedis::publish
.use Illuminate\Support\Facades\Redis; use Illuminate\Http\Request; class EventController extends Controller { public function publishEvent(Request $request) { Redis::publish('public.playground.1', json_encode($request)); } }
Whenever the
Redis::publish
method is called, Redis first checks if the channel is already created and if not, it creates one and publishes the message to the queue. We will be posting data to thepublishEvent
function using/api/publish
endpoint.Handling Events
Before creating the subscriber, let's first create the event that will be responsible for broadcasting the fetched data by subscribing to channels. The command
php artisan make:event PlaygroundEvent
will create an event file underapp\events
directory.<?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 PlaygroundEvent implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $data; public function __construct($data) { $this->data = $data; } public function broadcastOn(): Channel { return new Channel('public.playground.pusher'); } public function broadcastAs() { return 'playground'; } public function broadcastWith() { $response = json_decode($this->data, true); return [ 'color' => $response['color'], 'x_val' => $response['x_val'], 'y_val' => $response['y_val'], 'start_status' => $response['start_status'], 'end_status' => $response['end_status'] ]; } }
The event is initialized through the constructor that holds the data you want to broadcast; and will be broadcast to the
public.playground.pusher
channel with the nameplayground
.Subscribing to Channels
For the subscriber, you’ll be using a command as it is a continuous process and will not stop running until you stop it, or an error occurs. Let's create a command file under
app\Console\Commands
directory by running the command:php artisan make:command SubscribeCommand
<?php namespace App\Console\Commands; use App\Events\PlaygroundEvent; use Illuminate\Console\Command; use Illuminate\Support\Facades\Redis; class SubscribeCommand extends Command { protected $signature = 'redis:subscribe'; protected $description = 'Command description'; public function handle() { Redis::subscribe('public.playground.1', function ($data) { PlaygroundEvent::dispatch($data); }); } }
The code above asks Redis to listen to the
public.playground.1
channel and dispatch the previously createdPlaygroundEvent
event whenever a new data is recieved using theRedis:subscribe
method.Broadcasting Events
To show the changes in real-time, we will be using
beyondcode/laravel-websockets
andpusher/pusher-php-server
.After installing, configure pusher credentials in
.env
as shown below:PUSHER_APP_ID=livepost PUSHER_APP_KEY=livepost_key PUSHER_APP_SECRET=livepost_secret PUSHER_HOST= PUSHER_PORT=443 PUSHER_SCHEME=https PUSHER_APP_CLUSTER=mt1
The command:
php artisan websockets:serve
, will start the server on 6001 port. Now, if you serve the project on 8000 port and go to this url: http://localhost:8000/laravel-websockets, the laravel websocket interface will appear. After connecting to 6001 port, you shall see a list of events along with its details and time.After that, install
laravel-echo
andpusher-js
to listen to theplayground
channel to show changes to the subscribers. Uncomment the following code and make changes toresources\js\bootstrap.js
file.import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; window.Echo = new Echo({ broadcaster: 'pusher', key: 'livepost_key', cluster: 'mt1', wsHost: window.location.hostname, wsPort: 6001, encrypted: false, wssPort: 443, forceTLS: false, enabledTransports: ['ws', 'wss'], });
If you look at the javascript section of the provided CodePen, you will see three Mouse Events: Down, Move and Up. Therefore, when the Mouse Down event is triggered, we need to send the data with the start_status set to true. Accordingly, start_status and end_status will be set to false when it is listening to Mouse Move event. Lastly, after triggering the Mouse Up event, end_status must be set to true.
function publishMessage(beginPath, endPath) { let data = { x_val: mouseX, y_val: mouseY, color: context.strokeStyle, start_status: beginPath, end_status: endPath } let jsonData = JSON.stringify(data); fetch(`http://localhost:${window.location.port}/api/publish`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: jsonData }) .then(response => { if (response.ok) { console.log('Data sent'); } }) .catch(error => { console.error('Error:', error); }); }
The
publishMessage
method as shown above will be called inside the events and will send the data back to the controller function where the data gets published. To initiates a subscription to thepublic.playground.pusher
channel and show the changes, we will be using laravel-echo.const channel = window.Echo.channel('public.playground.pusher'); channel.subscribed(() => { console.log('subscribed to channel.'); }).listen('.playground', (event) => { mouseX = event.x_val; mouseY = event.y_val; context.strokeStyle = event.color; if (event.start_status) // mousedown event from publisher { context.beginPath(); context.moveTo(mouseX, mouseY); } else if (event.end_status) // mouseup event from publisher { isDrawing = false; } else // mousemove event from publisher { context.lineTo(mouseX, mouseY); context.stroke(); } });
Once the subscription is successful, it listens for events with the name
.playground
on the channel. Inside the event listener, it extracts information from the received event data and sets themouseX
,mouseY
, andcontext.strokeStyle
values based on it. It checks the status flags (start_status
andend_status
) in the event data to determine the type of drawing event. Ifstart_status
is true, it initiates a drawing path and sets the starting point. Ifend_status
is true, it indicates a mouseup event and stops drawing by settingisDrawing
to false. If neither start nor end status is true, it assumes a mousemove event and draws a line from the previous point to the current point.
Thus, Redis Pub/Sub maintains a Fire & Forget messaging pattern where the sender sends a message without expecting an explicit acknowledgment from the receiver that the message was received. As a result, one of the most prominent drawbacks of pub/sub is failed message delivery. Once a message is sent, there is no guarantee that the message was received or acted upon. These limitations should be considered when determining the suitability of Redis Pub/Sub as a message broker for a particular use case. On the contrary, if the system requires explicit acknowledgment of messages by the receiver, Redis Streams may come in handy.
Subscribe to my newsletter
Read articles from Nafisa Nawer directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by