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:
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:
It's a powerful tool that can be used in many different ways. The only limit is our imagination and creativity.
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.
Before Reverb, we had a few popular alternatives to build real-time applications in Laravel:
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.
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.
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.
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.
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.