Laravel Real-Time Notifications

laravel real time notification

In this post, we will take a look at how we can send real-time notifications with Laravel, the Laravel Websockets package, and Laravel Echo.

real time notification banner

HTTP is stateless. Usually, the client asks for a URL, and the server returns the data. Only a refresh of the page can load new information. Most of the time, this is enough, but sometimes we need more.

Today, many tasks happen on the backend, and we sometimes need to inform the user about it right away. So there is a use-case for triggering an action from the server instead of the client. Think of messaging in a chat or notification messages that pop up on the top of your dashboard.

To achieve this, our client could ask the server every second if something new happened, or you could make use of long polling. But the best solution is to create a new communication channel through WebSockets which works in both ways. Today we are going to build an application with real-time messaging. We will use a WebSocket solution called Laravel Websockets, built in PHP. Here is a preview of what we are going to built today:
laravel real time notification test

Installation

We start by creating a new Laravel 8 application. I always recommend using the Laravel Installer for this purpose.
				
					laravel new laravel-real-time-notifications
				
			
To achieve our goal of sending real-time notifications, we need to make three parts work together:
Let’s start with the WebSockets server.

Installing Laravel Websockets

Require the Laravel Websockets package. It works as a replacement for external services like Pusher. Many settings will refer to Pusher today but be reminded that we are not using it. We want our own solution.
				
					composer require beyondcode/laravel-websockets
				
			
We also need a package by Pusher.
				
					composer require pusher/pusher-php-server
				
			
Next, adapt your .env file. We want the BROADCAST_DRIVER to be pusher.
				
					BROADCAST_DRIVER=pusher
				
			
Note: Again I want to mention that we do not use the Pusher service. Our websockets server just has the same API.
And we need to set the Pusher credentials.
				
					PUSHER_APP_ID=12345
PUSHER_APP_KEY=12345
PUSHER_APP_SECRET=12345
PUSHER_APP_CLUSTER=mt1
				
			
We can define all those values ourselves. We are not actually using Pusher, but the package uses these keys, so it makes replacing Pusher (if you use it) as simple as possible.
Note: Make sure to use more random values when using in production.
The Laravel Websockets package comes with a migration file for storing statistics and a config file we need to adapt. Let’s publish them.
				
					php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"

				
			
This will create a new migration file that we can run. Make sure you have set up a database for this project and defined the DB credentials in the .env file. Afterward, we can run the migration.
				
					php artisan migrate
				
			
And here, we publish the config file of Laravel Websockets.
				
					php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

				
			
Now we are ready to start the WebSockets server.
				
					php artisan websockets:serve
				
			
To test that it is running, we can check the debugging dashboard under the endpoint laravel-websockets. You can click connect to see if the dashboard can connect to the WebSockets server.
laravel real time web sockets connect
After clicking connect, you should see that the dashboard is subscribed to some debugging channels like private-websockets-dashboard-api-message. This will tell you that the server is set up correctly.

Broadcast Notifications From Our Laravel Application

There are two ways we can send messages from our backend to the WebSockets server:

We will start with events because this is a little easier. Later we will check notifications as well.

Let’s create a new event with artisan.

				
					php artisan make:event RealTimeMessage
				
			
Here is what we need to change:
				
					<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class RealTimeMessage implements ShouldBroadcast
{
    use SerializesModels;

    public string $message;

    public function __construct(string $message)
    {
        $this->message = $message;
    }

    public function broadcastOn(): Channel
    {
        return new Channel('events');
    }
}

				
			
Note: You may noticed that we didn’t define a listener like we usually do with an event. This is because we are using the websockets server.
Before we can try sending this event, please adapt your broadcasting.php config file to use the following options:
				
					'options' => [
    'cluster' => env('PUSHER_APP_CLUSTER'),
    'encrypted' => false,
    'host' => '127.0.0.1',
    'port' => 6001,
    'scheme' => 'http'
],
				
			
With these options, we make sure that when we broadcast something from our Laravel application, it gets sent to our WebSockets server.

Note: We do not use TLS for our demo, but we will take a look at that later as well.

Now we can trigger our created event RealTimeMessage with the global event helper. I recommend using tinker or Tinkerwell to do that for our demo, but you could also create a route and run the command there.
				
					event(new App\Events\RealTimeMessage('Hello World'));       
				
			
Of course, in a real application, you would run this inside a controller or action class. After running this command, you should see a new entry on the debug dashboard.
laravel real time websockets event sent

Listen To Messages From Our Front-end

We have made sure that our sent event is broadcasted to the WebSockets server. But now we want to listen to it so that we can use the message on our front-end. We are going to use the JavaScript library Laravel Echo for that purpose. Let’s start by installing it together with the pusher library, which we will need as well.
				
					npm install --save-dev laravel-echo pusher-js
				
			
The resouces/js/bootstrap.js file of Laravel already contains a code snippet for creating a new instance of Laravel Echo we can use. Comment it in and add the wsHost and wsPort.
				
					import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: false,
    wsHost: window.location.hostname,
    wsPort: 6001,
});
				
			
The bootstrap.js file is required by Laravel’s main resources/js/app.js file. This means we need to run npm run dev to bundle our Echo code with the main JS file. Now import the script. We are going to use Laravel’s welcome view for our tutorial. So add the app.js file into your view, right before the end of the body tag.
				
					<script src="{{ asset('js/app.js') }}"></script>
				
			
Also, open a new script tag where we create our Echo listener. We listen to the channel events and for the class name of the event we created. If we receive an update, we write a message to the console.
				
					<script>
    Echo.channel('events')
        .listen('RealTimeMessage', (e) => console.log('RealTimeMessage: ' + e.message));
</script>
				
			
When you refresh the browser’s welcome page, you should also get an update on the debug dashboard.
laravel real time websockets channel subscribe
It shows that we are successfully subscribed to the events channel. This means we are ready to give it a real try!

Sending Real-Time Messages

Now that we prepared our event, the WebSockets server, and our JavaScript listener, we can give a real try. So back inside tinker or Tinkerwell, we can trigger our event again.
				
					event(new App\Events\RealTimeMessage('Hello World'));
				
			
Now we not only see the message in the debug dashboard but also in the console output. So we were able to receive data on our front-end, without refreshing the page or making an ajax call. This is our first real-time message. 🥳

Let's Talk Privately

Next to sending messages to a public channel, we can also use a private one with events. Let’s change our RealTimeMessage event to use a private channel.
				
					public function broadcastOn(): Channel
{
    return new PrivateChannel('events');
}
				
			

Now send the event again.

Note: If you are using Laravel Tinker, make sure to restart it because of the changes we made in our event.

laravel real time debug private channel
As you can see in the debug dashboard, our message was now sent on a private channel. Nice! But now we also need to listen to a private channel. Luckily, Laravel Echo lets us also change the channel method to private.
				
					Echo.private('events')
    .listen('RealTimeMessage', (e) => console.log('Private RealTimeMessage: ' + e.message));
				
			

But when you refresh the welcome page, you see that we get an error.

The problem here is that private channels need to be authenticated. That’s what Laravel Echo is trying to do by requesting the /broadcasting/auth endpoint. But the endpoint is not defined. We need to include it by uncommenting the BroadcastServericeProvider in our app.php config file.
				
					/*
 * Application Service Providers...
 */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\BroadcastServiceProvider::class, // We enabled this class
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
				
			
Refresh the page again, and you will see the error from before is gone, but of course, there is a new one 🙂
laravel real time auth 419
  The endpoint is now defined, but it returns a 419 error. This one is about the csrf token. We need it to verify this request. Here we don’t have a form where we usually use it, but we can add it to our HTML head in a meta tag.
				
					<meta name="csrf-token" content="{{ csrf_token() }}">
				
			

Again we were able to solve a problem, and also we see a new error. But trust me, we are getting close now 🙂

laravel real time websockets event sent
This time we received a 403 error telling us we are not allowed to listen to the private channel. This is good because we haven’t told Laravel yet who is allowed to listen to this private channel. Next to the broadcast routes, the BroadcastServiceProvider also activates the routes/channels.php file. Here is where we define who is allowed to access a private channel. There is already an example, but we add our own channel check now.
				
					Broadcast::channel('events', function ($user) {
    return true;
});
				
			
We tell Laravel that we do not have specific rules for authenticating who can subscribe to our private channel. But when you refresh the welcome page, you will still see a 403 error. This is because one thing is the same for every private channel: There must be a logged-in user. So let’s add a new user to the database. I’m using Tinkerwell again here. We do not care about the user details.
				
					User::create([
    'name' => 'Test user',
    'email' => 'test@test.at',
    'password' => bcrypt('test'),
]);
				
			
We can log this user into our application before we return the welcome view in the web.php route file. Also, make sure to import the User class before you use it.
				
					Route::get('/', function () {
    auth()->login(User::first());

    return view('welcome');
});
				
			

This will log in the first user from our users table. Reload the welcome page, and you will no longer find an error in the console. You will also see that we are now subscribed to the private events channel in the debug dashboard.

Trigger the event once again, and we will receive the private message output in the console.

				
					event(new App\Events\RealTimeMessage('Hello World'));
				
			
laravel realtime private channel message console

Notifications

The title of this blog contains Notifications, so what about them? You might know I’m a big fan of Laravel’s notification system, so for sure, we are talking about them too. So next to events, we can use notifications to send data to our WebSockets server. So let’s create a new one.
				
					php artisan make:notification RealTimeNotification
				
			

Here is what we need to change:

				
					<?php

namespace App\Notifications;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Notifications\Notification;

class RealTimeNotification extends Notification implements ShouldBroadcast
{

    public string $message;

    public function __construct(string $message)
    {
        $this->message = $message;
    }

    public function via($notifiable): array
    {
        return ['broadcast'];
    }

    public function toBroadcast($notifiable): BroadcastMessage
    {
        return new BroadcastMessage([
            'message' => "$this->message (User $notifiable->id)"
        ]);
    }
}
				
			
Since notifications are always connected to a notifiable model (like a user), we have access to it inside the toBroadcast method. This way I can add the user id to the message we sent.
Note: There are also on-demand notifications where you do not need a notifiable, but this is not supported in combination with broadcasting.
Alright, time to trigger our notification. Again, we are using the one user we have in our database.
				
					$user = User::first();

$user->notify(new App\Notifications\RealTimeNotification('Hello World'));
				
			
laravel subscribed

You probably noticed that we did not define a channel name with our notification as we did in our event. This is because there is a default pattern for the channel name of a notification notifiable-class.key. In our case, this would be App.Models.User.1. And when you take a look at the Web dashboard, you find a message triggered by our notification to the channel Channel: private-App.Models.User.1.

Next, let’s subscribe to this channel in our front-end. Laravel echo has a notification method we can make use of.
				
					Echo.private('App.Models.User.1')
    .notification((notification) => {
        console.log(notification.message);
    });
				
			
You should be able to refresh the welcome page now without seeing any errors. Since we are using a private channel again, Laravel Echo will try to authenticate our subscribing request to this channel. This is where the other code-snippet from the channels.php file comes into play.
				
					Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});
				
			
It will make sure when you try to subscribe to a specific user’s private channel that the provided id is the same as the logged-in user. You can easily force an error when you try to listen to a different user id like:  
				
					Echo.private('App.Models.User.99')
    .notification((notification) => {
        console.log(notification.message);
    });
				
			
When you refresh the welcome page, you will see that we get a 403 error again. But now, let’s finish this example by triggering the notification also now.
laravel real notification console output
And this is how we can broadcast real-time messages with notifications as well. The benefit of notifications is that you can send them through multiple channels. So you can send a real-time message to the user’s dashboard about a newly created invoice and send an email while only using one notification class.

Conclusion

I hope this long article could give you a great start into real-time messaging with Laravel.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

Thank UU