Back to Course |
Laravel Reverb: Four "Live" Practical Examples

"Live" Notification with No Page Reload

Laravel Reverb is one of the newest first-party packages from the Laravel team. With Reverb, we can show page changes in real-time, like this message to users:

That message appears on the screen without refreshing the page or making API calls.

But how does it work? Let's dive into it, with first two practical examples:

  • Sending a global message "The system will go down for maintenance"
  • Sending a private message for a specific user "Your export is finished"

What is Reverb?

Reverb was created to solve the gap between back-end and front-end communications. It's a real-time messaging system that allows us to listen to back-end events and react to them on the front-end.

Here are a few examples of what we can do with Reverb:

  • Build real-time notifications about new messages, comments, reports, or system events.
  • Build real-time chat applications.
  • Build real-time dashboards with live data.
  • Inform users about changes in the system, such as new products, reports, etc.
  • Create presence systems to show who is online and who is not.
  • And many more...

It's a powerful tool that can be used in many different ways. The only limit is our imagination and creativity.


Why Reverb Instead of Periodical Refresh?

But why use Reverb instead of a simple API endpoint? We can call an API endpoint to get the latest data, right? And if we need to wait for something to finish, we can always use Polling, calling the server every minute or so, right?

Well, yes, we can. But we can also use a sword to cut our bread, right? But we don't. We use a knife. Why? Because it's more efficient and easier to use. It's the same with Reverb. Here's why:

With Polling, we send requests to the server every X seconds/minutes to check for new data.

Sounds good. We solved the problem.

But what if we have 1000 users? And then 100 000 users? We will send N (number of users) requests every X seconds. That's a lot of requests!!!

Instead, with Reverb, we can send a single request to the server, and that's it!

It's based on WebSockets. As shown in the image above, it will create a WebSocket connection between the server and the client.

This connection allows our server and client to communicate in real time without sending multiple requests. As long as the connection is open, we can freely send messages/data and avoid creating unnecessary requests that will slow down our server.


Reverb Alternatives?

Before Reverb, we had a few popular alternatives to build real-time applications in Laravel:

  • Pusher - third-party service (paid) that allows us to build real-time applications.
  • Laravel WebSockets - a package from BeyondCode (Now Archived)
  • Soketi - alternative to Pusher, but free (self-hosted)

Reverb was created to simplify the process of configuration and owning that problem as a first-party package.

Now, let's see how we can use Reverb in our Laravel application.


Project Setup

We will use Laravel 11 for this tutorial with Breeze as our authentication system. From there, we have a simple "Dashboard" page:

From here, we will install Reverb and create a few simple real-time events to show how it works.


Installing Reverb

To install Reverb, we have to call this command:

php artisan install:broadcasting

This will ask us if we want to install Reverb, enter yes and press Enter.

Once the package installs, it will ask if we want Node dependencies installed. Enter yes and press Enter.

That's it! Reverb is installed and ready to be used. No more configuration is needed at this point.


Creating our First Event - Global System Maintenance

Our first event will be targeting all users in our system - we will send a "System maintenance" message to all users:

To create an event, we have to run this command:

php artisan make:event SystemMaintenanceEvent

This will create a new event in our app/Events folder. We have to modify a few things in this file:

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 SystemMaintenanceEvent
class SystemMaintenanceEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
 
/**
* Create a new event instance.
*/
public function __construct()
public function __construct(public string $time)
{
//
}
 
/**
* Get the channels the event should broadcast on.
*
* @return array<int, Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
new Channel('system-maintenance'),
];
}
}

Then we have to modify our routes/channels.php file:

// ...
 
Broadcast::channel('system-maintenance', function () {
// Public Channel
});

We added a new channel to our broadcasting file. Next, we need to listen for this event on our front end:

resources/views/layouts/app.blade.php

{{-- ... --}}
 
<script>
window.addEventListener('DOMContentLoaded', function () {
window.Echo.channel('system-maintenance')
.listen('SystemMaintenanceEvent', (event) => {
console.log(event)
});
});
</script>
</body>
</html>

Then, we want to create a new command to trigger this event:

php artisan make:command SystemNotifyMaintenanceCommand

And modify the command file:

app/Console/Commands/SystemNotifyMaintenanceCommand.php

 
use App\Events\SystemMaintenanceEvent;
use Illuminate\Console\Command;
 
class SystemNotifyMaintenanceCommand extends Command
{
protected $signature = 'system:notify-maintenance';
 
protected $description = 'Command description';
 
public function handle(): void
{
$time = $this->ask('When should it happen?');
 
event(new SystemMaintenanceEvent($time));
}
}

Once this is done, we can run the Reverb server:

php artisan reverb:start

Then, we need to open the dashboard page and trigger the event by calling the command. Once that is done, we should see the event in our console:

From here, we can do whatever we want with this event. In our case, we want to add a warning message to our dashboard:

resources/views/layouts/app.blade.php

{{-- ... --}}
 
window.Echo.channel('system-maintenance')
.listen('SystemMaintenanceEvent', (event) => {
let location = document.getElementsByTagName('main')[0];
let div = document.createElement('div');
div.style.width = '100%';
div.style.height = '32px';
div.style.textAlign = 'center';
div.style.lineHeight = '32px';
div.style.background = '#ffab44';
div.innerHTML = 'WARNING: The system will go down for maintenance at ' + event.time + '.';
location.insertBefore(div, location.firstChild);
});
 
{{-- ... ---}}

Now, if we reload the page and trigger the event, we should see the warning message at the top of our dashboard:

That's it! We have created our first real-time event with Reverb!

But wait, this event was global and public. See in the top-right corner, I'm logged in with different users, and they both see the message.

This means our event was sent to all users in the system. But what if we want to send it to a specific user? Let's create a new event for that.


Creating a Private Event - Informing User About Finished Export

To create a private event, we will create a new event:

php artisan make:event ExportFinishedEvent

Then, we have to modify the event file:

 
namespace App\Events;
 
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;
 
class ExportFinishedEvent
class ExportFinishedEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
 
/**
* Create a new event instance.
*/
public function __construct(public int $user_id)
{
//
}
 
/**
* Get the channels the event should broadcast on.
*
* @return array<int, Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('App.Models.User.'.$this->user_id),
];
}
}

This will create a new event in an existing channel - App.Models.User.{user_id}. Only the user with the specified ID will receive this event.

Note: This channel is created automatically by installing Reverb.

Then we want to build two things - a controller and a job to trigger this event:

php artisan make:controller StartExportController

app/Http/Controllers/StartExportController.php

 
use App\Jobs\ExportPdfDataJob;
 
class StartExportController extends Controller
{
public function __invoke()
{
dispatch(new ExportPdfDataJob(auth()->id()));
 
return redirect()->back();
}
}

Then we have to create a job:

php artisan make:job ExportPdfDataJob

app/Jobs/ExportPdfDataJob.php

 
use App\Events\ExportFinishedEvent;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
 
class ExportPdfDataJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
public function __construct(public int $userId)
{
}
 
public function handle(): void
{
sleep(5);
 
event(new ExportFinishedEvent($this->userId, 'file.pdf'));
}
}

Once this is done, we can listen for this event on our front end:

resources/views/layouts/app.blade.php

{{-- ... --}}
 
window.Echo.private('App.Models.User.' + {{ auth()->id() }})
.listen('ExportFinishedEvent', (event) => {
console.log(event)
});
 
{{-- ... ---}}

Now, if we trigger this event at the end of our export job, we can see it in our console:

From here, we can display a banner to the user that the export is finished:

resources/views/layouts/app.blade.php

{{-- ... --}}
 
window.Echo.private('App.Models.User.' + {{ auth()->id() }})
.listen('ExportFinishedEvent', (event) => {
let location = document.getElementsByTagName('main')[0];
let div = document.createElement('div');
div.style.width = '100%';
div.style.height = '32px';
div.style.textAlign = 'center';
div.style.lineHeight = '32px';
div.style.background = '#44ff44';
div.innerHTML = 'SUCCESS: Your export is finished. You can download it <a href="' + event.file_path + '">here</a>.';
location.insertBefore(div, location.firstChild);
});
 
{{-- ... ---}}

Now, if we trigger this event in our job, we should see the banner at the top of our dashboard:

And here's the beauty of this - only the user that triggered the event will see this banner. As you can see below, the other user does NOT see the message.

That's it! We have created a private event with Reverb!

The full repository with source is here on GitHub.


The following lessons will create more examples of private and public events.