How to add real-time notifications to Laravel using Pusher

A translation of the article was prepared especially for students of the “Framework Laravel” course .




A modern web user expects to be informed about everything that happens in the application. You would not want to be the website that does not even have a drop-down list of notifications, which can now be found not only on all social networking sites, but generally everywhere these days.

Fortunately, with Laravel and Pusher, the implementation of this functionality is quite simple.

Real time notifications


In order to provide a positive user experience, notifications should be displayed in real time. One approach is to regularly send an AJAX request to the server and receive the latest notifications, if any.

The best approach is to use the capabilities of WebSockets and receive notifications when they are sent. This is exactly what we are going to implement in this article.

Pusher


Pusher is a web service for integrating real-time bi-directional functionality through WebSockets into web and mobile applications.

It has a very simple API, but we're going to make using it even easier with Laravel Broadcasting and Laravel Echo.

In this article, we are going to add real-time notifications to an existing blog.

Project


Initialization


First, we clone a simple Laravel blog:

git clone   https://github.com/marslan-ali/laravel-blog

Then we will create the MySQL database and set up the environment variables to give the application access to the database.

Let's copy env.examplein .envand update the variables associated with the database.

cp .env.example .envDB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

Now let's install the project dependencies using

composer install

And run the migration and populate command to populate the database with some data:

php artisan migrate --seed

If you run the application and go to /posts, you can see a list of generated posts.

Check the application, register the user and create some messages. This is a very simple application, but it is great for demonstration.

Subscribe to Users


We would like to give users the opportunity to subscribe to each other, so we must create a many-to-many relationship between users in order to realize this.

Let's create a pivot table that links users to users. Let's make a new migration followers:

php artisan make:migration create_followers_table --create=followers

We need to add several fields to this migration: user_idto represent the user who is subscribed, and a field follows_idto represent the user that they are subscribed to.

Update the migration as follows:

public function up()
{
    Schema::create('followers', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->index();
        $table->integer('follows_id')->index();
        $table->timestamps();
    });
}

Now let's move on to creating the table:

php artisan migrate

Let's add relationship methods to the model User.

// ...

class extends Authenticatable
{
    // ...

    public function followers() 
    {
        return $this->belongsToMany(self::class, 'followers', 'follows_id', 'user_id')
                    ->withTimestamps();
    }

    public function follows() 
    {
        return $this->belongsToMany(self::class, 'followers', 'user_id', 'follows_id')
                    ->withTimestamps();
    }
}

Now that the model Userhas the necessary relationships, it followersreturns all the subscribers of the user, and followsreturns all those to whom the user is subscribed.

We will need some auxiliary functions that allow the user to subscribe to other users - follow, and to check whether the user is subscribed to a specific user - isFollowing.

// ...

class extends Authenticatable
{
    // ...

    public function follow($userId) 
    {
        $this->follows()->attach($userId);
        return $this;
    }

    public function unfollow($userId)
    {
        $this->follows()->detach($userId);
        return $this;
    }

    public function isFollowing($userId) 
    {
        return (boolean) $this->follows()->where('follows_id', $userId)->first(['id']);
    }

}

Fine. After preparing the model, you need to make a list of users.

a list of users


Let's start by identifying the necessary routes.

/...
Route::group(['middleware' => 'auth'], function () {
    Route::get('users', 'UsersController@index')->name('users');
    Route::post('users/{user}/follow', 'UsersController@follow')->name('follow');
    Route::delete('users/{user}/unfollow', 'UsersController@unfollow')->name('unfollow');
});

Then it's time to create a new controller for users:

php artisan make:controller UsersController

We will add a method to it index:

// ...
use App\User;
class UsersController extends Controller
{
    //..
    public function index()
    {
        $users = User::where('id', '!=', auth()->user()->id)->get();
        return view('users.index', compact('users'));
    }
}

The method needs to be introduced. Let's create a view users.indexand put the following markup in it:

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="col-sm-offset-2 col-sm-8">

            <!-- Following -->
            <div class="panel panel-default">
                <div class="panel-heading">
                    All Users
                </div>

                <div class="panel-body">
                    <table class="table table-striped task-table">
                        <thead>
                        <th>User</th>
                        <th> </th>
                        </thead>
                        <tbody>
                        @foreach ($users as $user)
                            <tr>
                                <td clphpass="table-text"><div>{{ $user->name }}</div></td>
                                @if (auth()->user()->isFollowing($user->id))
                                    <td>
                                        <form action="{{route('unfollow', ['id' => $user->id])}}" method="POST">
                                            {{ csrf_field() }}
                                            {{ method_field('DELETE') }}

                                            <button type="submit" id="delete-follow-{{ $user->id }}" class="btn btn-danger">
                                                <i class="fa fa-btn fa-trash"></i>Unfollow
                                            </button>
                                        </form>
                                    </td>
                                @else
                                    <td>
                                        <form action="{{route('follow', ['id' => $user->id])}}" method="POST">
                                            {{ csrf_field() }}

                                            <button type="submit" id="follow-user-{{ $user->id }}" class="btn btn-success">
                                                <i class="fa fa-btn fa-user"></i>Follow
                                            </button>
                                        </form>
                                    </td>
                                @endif
                            </tr>
                        @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
@endsection

Now you can visit the page /usersto see the list of users.

Follow and Unfollow


In UsersControllermissing methods followand unfollow. Let's implement them to complete this part.

//...
class UsersController extends Controller
{
    //...

    public function follow(User $user)
    {
        $follower = auth()->user();
        if ($follower->id == $user->id) {
            return back()->withError("You can't follow yourself");
        }
        if(!$follower->isFollowing($user->id)) {
            $follower->follow($user->id);

            //  
            $user->notify(new UserFollowed($follower));

            return back()->withSuccess("You are now friends with {$user->name}");
        }
        return back()->withError("You are already following {$user->name}");
    }

    public function unfollow(User $user)
    {
        $follower = auth()->user();
        if($follower->isFollowing($user->id)) {
            $follower->unfollow($user->id);
            return back()->withSuccess("You are no longer friends with {$user->name}");
        }
        return back()->withError("You are not following {$user->name}");
    }
}

We are done with this functionality. Now we can subscribe to users and unsubscribe from them on the page /users.

Notifications


Laravel provides an API for sending notifications across multiple channels. Email, SMS, web notifications and any other types of notifications can be sent using the Notification class .

We will have two types of notifications:

  • Subscription notification: sent to the user when another user subscribes to it
  • Post notification: sent to subscribers of this user when a new post is published.

Subscription Notification


Using artisan commands, we can generate a migration for notifications:

php artisan notifications:table

Let's do the migration and create this table.

php artisan migrate

We will start with subscription notifications. Let's run this command to create a notification class:

php artisan make:notification UserFollowed

Then we modify the notification class file that we just created:

class UserFollowed extends Notification implements ShouldQueue
{
    use Queueable;

    protected $follower;

    public function __construct(User $follower)
    {
        $this->follower = $follower;
    }

    public function via($notifiable)
    {
        return ['database'];
    }

    public function toDatabase($notifiable)
    {
        return [
            'follower_id' => $this->follower->id,
            'follower_name' => $this->follower->name,
        ];
    }
}

With these few lines of code, we can already achieve a lot. First, we require that the instance $followerbe implemented when this notification is generated.

Using the method via, we tell Laravel to send this notification through the channel database. When Laravel encounters this, it creates a new entry in the notification table.

user_idand typenotifications are set automatically, plus we can expand the notification with additional data. That's what it is for toDatabase. The returned array will be added to the datanotification field .

And finally, thanks to the implementationShouldQueue, Laravel will automatically place this notification in a queue that will run in the background, which will speed up the response. This makes sense because we will add HTTP calls when we use Pusher later.

Let's implement a user subscription notification.

// ...
use App\Notifications\UserFollowed;
class UsersController extends Controller
{
    // ...
    public function follow(User $user)
    {
        $follower = auth()->user();
        if ( ! $follower->isFollowing($user->id)) {
            $follower->follow($user->id);

            //  ,   
            $user->notify(new UserFollowed($follower));

            return back()->withSuccess("You are now friends with {$user->name}");
        }

        return back()->withSuccess("You are already following {$user->name}");
    }

    //...
}

We can call the notify method for the User model because it already uses the Notifiable trait.

Any model you want to notify should use it to gain access to the notify method.

We mark the notification as read

Notifications will contain some information and a link to the resource. For example: when the user receives a notification about a new message, the notification should contain informative text, redirect the user to the message when pressed and be marked as read.

We are going to create a layer that will check if there is an occurrence in the request ?read=notification_idand marks it as read.

Let's make this layer with the following command:

php artisan make:middleware MarkNotificationAsRead

Then let's put this code in the handleinterlayer method :

class MarkNotificationAsRead
{
    public function handle($request, Closure $next)
    {
        if($request->has('read')) {
            $notification = $request->user()->notifications()->where('id', $request->read)->first();
            if($notification) {
                $notification->markAsRead();
            }
        }
        return $next($request);
    }
}

In order for our layer to be executed for each request, we will add it to $middlewareGroups.

//...
class Kernel extends HttpKernel
{
    //...
    protected $middlewareGroups = [
        'web' => [
            //...
            \App\Http\Middleware\MarkNotificationAsRead::class,
        ],
        // ...
    ];
    //...
}

After that, let's get the notifications displayed.

Display notifications


We need to show the notification list using AJAX, and then update it in real time using Pusher. First, let's add a method notificationsto the controller:

// ...
class UsersController extends Controller
{
    // ...
    public function notifications()
    {
        return auth()->user()->unreadNotifications()->limit(5)->get()->toArray();
    }
}

This code will return the last 5 unread notifications. We just need to add a route to make it accessible.

//...
Route::group([ 'middleware' => 'auth' ], function () {
    // ...
    Route::get('/notifications', 'UsersController@notifications');
});

Now add a drop-down list for notifications in the header.

<head>
    <!-- // ... // -->
    <!-- Scripts -->
    <script>
        window.Laravel = <?php echo json_encode([
            'csrfToken' => csrf_token(),
        ]); ?>
    </script>
    <!--   id     JavaScript -->
    @if(!auth()->guest())
        <script>
            window.Laravel.userId = <?php echo auth()->user()->id; ?>
        </script>
    @endif
</head>
<body>
    <!-- // ... // -->
    @if (Auth::guest())
        <li><a href="{{ url('/login') }}">Login</a></li>
        <li><a href="{{ url('/register') }}">Register</a></li>
    @else
        <!-- // add this dropdown // -->
        <li class="dropdown">
            <a class="dropdown-toggle" id="notifications" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
                <span class="glyphicon glyphicon-user"></span>
            </a>
            <ul class="dropdown-menu" aria-labelledby="notificationsMenu" id="notificationsMenu">
                <li class="dropdown-header">No notifications</li>
            </ul>
        </li>
<!-- // ... // -->

We also added a global variable window.Laravel.userIdto the script to get the current user ID.

JavaScript and SASS


We are going to use Laravel Mix to compile JavaScript and SASS. First, we need to install npm packages.

npm install

Now let's add this code to app.js:

window._ = require('lodash');
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
var notifications = [];
const NOTIFICATION_TYPES = {
    follow: 'App\\Notifications\\UserFollowed'
};

This is just initialization. We are going to use notifications to store all notification objects, whether they are retrieved through AJAX or Pusher.

As you probably already guessed, it NOTIFICATION_TYPEScontains notification types.

Now let's get (“GET”) notifications via AJAX.

//...
$(document).ready(function() {
    // ,      
    if(Laravel.userId) {
        $.get('/notifications', function (data) {
            addNotifications(data, "#notifications");
        });
    }
});
function addNotifications(newNotifications, target) {
    notifications = _.concat(notifications, newNotifications);
    //    5 
    notifications.slice(0, 5);
    showNotifications(notifications, target);
}

Thanks to this code, we receive the latest notifications from our API and put them in a drop-down list.

Inside, addNotificationswe combine existing notifications with new ones using Lodash , and take only the last 5, which will be shown.

We need a few more functions to finish the job.

//...
function showNotifications(notifications, target) {
    if(notifications.length) {
        var htmlElements = notifications.map(function (notification) {
            return makeNotification(notification);
        });
        $(target + 'Menu').html(htmlElements.join(''));
        $(target).addClass('has-notifications')
    } else {
        $(target + 'Menu').html('<li class="dropdown-header">No notifications</li>');
        $(target).removeClass('has-notifications');
    }
}

This function creates a line of all notifications and places it in the drop-down list.
If no notifications have been received, simply “No notifications” is displayed.

It also adds a class to the drop-down button, which simply changes color when there are notifications. A bit like Github notifications.

Finally, some helper functions for creating notification strings.

//...
//   
function makeNotification(notification) {
    var to = routeNotification(notification);
    var notificationText = makeNotificationText(notification);
    return '<li><a href="' + to + '">' + notificationText + '</a></li>';
}
//        
function routeNotification(notification) {
    var to = '?read=' + notification.id;
    if(notification.type === NOTIFICATION_TYPES.follow) {
        to = 'users' + to;
    }
    return '/' + to;
}
//        
function makeNotificationText(notification) {
    var text = '';
    if(notification.type === NOTIFICATION_TYPES.follow) {
        const name = notification.data.follower_name;
        text += '<strong>' + name + '</strong> followed you';
    }
    return text;
}

Now we just add this to our file app.scss:

//... 
#notifications.has-notifications {
  color: #bf5329
}

Let's compile the assets:

npm run dev

Now if you try to subscribe to a user, he will receive a notification. When he clicks on it, he will be redirected to /users, and the notification itself disappears.

New Post Notification


We are going to notify subscribers when a user publishes a new post.

Let's start by creating a notification class.

php artisan make:notification NewPost

Let's modify the generated class as follows:

// ..
use App\Post;
use App\User;
class NewArticle extends Notification implements ShouldQueue
{
    // ..
    protected $following;
    protected $post;
    public function __construct(User $following, Post $post)
    {
        $this->following = $following;
        $this->post = $post;
    }
    public function via($notifiable)
    {
        return ['database'];
    }
    public function toDatabase($notifiable)
    {
        return [
            'following_id' => $this->following->id,
            'following_name' => $this->following->name,
            'post_id' => $this->post->id,
        ];
    }
}

Next we need to send a notification. There are several ways to do this.

I like to use Eloquent watchers.

Let's create an observer for the Post and listen to its events. We will create a new class:app/Observers/PostObserver.php

namespace App\Observers;
use App\Notifications\NewPost;
use App\Post;
class PostObserver
{
    public function created(Post $post)
    {
        $user = $post->user;
        foreach ($user->followers as $follower) {
            $follower->notify(new NewPost($user, $post));
        }
    }
}

Then register the observer in AppServiceProvider:

//...
use App\Observers\PostObserver;
use App\Post;
class AppServiceProvider extends ServiceProvider
{
    //...
    public function boot()
    {
        Post::observe(PostObserver::class);
    }
    //...
}

Now we just need to format the message for display in JS:

// ...
const NOTIFICATION_TYPES = {
    follow: 'App\\Notifications\\UserFollowed',
    newPost: 'App\\Notifications\\NewPost'
};
//...
function routeNotification(notification) {
    var to = `?read=${notification.id}`;
    if(notification.type === NOTIFICATION_TYPES.follow) {
        to = 'users' + to;
    } else if(notification.type === NOTIFICATION_TYPES.newPost) {
        const postId = notification.data.post_id;
        to = `posts/${postId}` + to;
    }
    return '/' + to;
}
function makeNotificationText(notification) {
    var text = '';
    if(notification.type === NOTIFICATION_TYPES.follow) {
        const name = notification.data.follower_name;
        text += `<strong>${name}</strong> followed you`;
    } else if(notification.type === NOTIFICATION_TYPES.newPost) {
        const name = notification.data.following_name;
        text += `<strong>${name}</strong> published a post`;
    }
    return text;
}

And voila! Users are notified of subscriptions and new posts! Try it yourself!

Exit in real time with Pusher


It's time to use Pusher to receive real-time notifications via web sockets.

Register for a free Pusher account at pusher.com and create a new app.

...
BROADCAST_DRIVER=pusher
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_APP_ID=

Set the parameters for your account in the configuration file broadcasting:

//...
    'connections' => [
            'pusher' => [
                //...
                'options' => [
                    'cluster' => 'eu',
                    'encrypted' => true
                ],
            ],
    //...

Then we will register App\Providers\BroadcastServiceProviderin the array providers.

// ...
'providers' => [
    // ...
    App\Providers\BroadcastServiceProvider
    //...
],
//...

Now we need to install the PHP SDK and Laravel Echo from Pusher:

composer require pusher/pusher-php-server
npm install --save laravel-echo pusher-js

We need to set up notification data for broadcast. Let's modify the notification UserFollowed:

//...
class UserFollowed extends Notification implements ShouldQueue
{
    // ..
    public function via($notifiable)
    {
        return ['database', 'broadcast'];
    }
    //...
    public function toArray($notifiable)
    {
        return [
            'id' => $this->id,
            'read_at' => null,
            'data' => [
                'follower_id' => $this->follower->id,
                'follower_name' => $this->follower->name,
            ],
        ];
    }
}

And NewPost:

//...
class NewPost extends Notification implements ShouldQueue
{
    //...
    public function via($notifiable)
    {
        return ['database', 'broadcast'];
    }
    //...
    public function toArray($notifiable)
    {
        return [
            'id' => $this->id,
            'read_at' => null,
            'data' => [
                'following_id' => $this->following->id,
                'following_name' => $this->following->name,
                'post_id' => $this->post->id,
            ],
        ];
    }
}

The last thing we need to do is update our JS. Open app.jsand add the following code

// ...
window.Pusher = require('pusher-js');
import Echo from "laravel-echo";
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key',
    cluster: 'eu',
    encrypted: true
});
var notifications = [];
//...
$(document).ready(function() {
    if(Laravel.userId) {
        //...
        window.Echo.private(`App.User.${Laravel.userId}`)
            .notification((notification) => {
                addNotifications([notification], '#notifications');
            });
    }
});

And that is all. Notifications are added in real time. Now you can play with the application and see how notifications are updated.

Conclusion


Pusher has a very user-friendly API that makes real-time event implementation incredibly simple. In combination with Laravel notifications, we can send notifications via several channels (email, SMS, Slack, etc.) from one place. In this guide, we added functionality to track user activity in a simple blog and improved it using the above tools to get some smooth real-time functionality.

Pusher and Laravel have much more notifications: in a tandem, services allow you to send pub / sub messages in real time to browsers, mobile phones and IOT devices. There is also an API for obtaining online / offline user status.

Please see their documentation ( Pusher docs, Pusher tutorials , Laravel docs ) to learn more about their use and true potential.

If you have any comments, questions or recommendations, feel free to share them in the comments below!



Learn more about the course.



All Articles