Laravel with Redis Pub/Sub: A Deep Dive into Real-Time Event Handling

Nafisa NawerNafisa Nawer
6 min read

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 with Redis::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 the publishEvent 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 under app\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 name playground.

  • 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 created PlaygroundEvent event whenever a new data is recieved using the Redis:subscribe method.

  • Broadcasting Events

    To show the changes in real-time, we will be using beyondcode/laravel-websockets and pusher/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 and pusher-js to listen to the playground channel to show changes to the subscribers. Uncomment the following code and make changes to resources\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 the public.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 the mouseX, mouseY, and context.strokeStyle values based on it. It checks the status flags (start_status and end_status) in the event data to determine the type of drawing event. If start_status is true, it initiates a drawing path and sets the starting point. If end_status is true, it indicates a mouseup event and stops drawing by setting isDrawing 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.

0
Subscribe to my newsletter

Read articles from Nafisa Nawer directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nafisa Nawer
Nafisa Nawer