How to add real-time notifications to Laravel using Pusher

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


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.example in .env and update the database related variables.

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 the 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_id to represent the user who is subscribed and the field follows_id to represent the user you are following.

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 User has the necessary relationship followers returns all subscribers of the user, and follows returns all the user is following.

We will need some auxiliary functions that allow the user to subscribe to other users – follow, and check if 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 AppUser;
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.index and put the following markup in it:

@extends('layouts.app')

@section('content')
    
All Users
@foreach ($users as $user) @if (auth()->user()->isFollowing($user->id)) @else @endif @endforeach
User
{{ $user->name }}
{{ csrf_field() }} {{ method_field('DELETE') }}
{{ csrf_field() }}
@endsection

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

Follow and Unfollow

IN UsersController missing methods follow and 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}");
    }
}

With this functionality we are done. 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 class Notification.

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 $follower was implemented when this notification was generated.

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

user_id and type notifications are set automatically, plus we can expand the notification with additional data. That’s what it’s for toDatabase. The returned array will be added to the field data notifications.

And finally, thanks to the implementation ShouldQueue, 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 AppNotificationsUserFollowed;
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.

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 an interlayer that will check if the request contains an entry ?read=notification_id and 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 a method handle interlayers:

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' => [
            //...
            AppHttpMiddlewareMarkNotificationAsRead::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 notifications to 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.


    
      
    
    @if (! auth () -> guest ())  @endif


    
    
    @if (Auth :: guest ())
        
  • Login
  • Register
  • @else

    We also added a global variable window.Laravel.userId to 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, regardless of whether they are retrieved through AJAX or Pusher.

    As you probably already guessed, NOTIFICATION_TYPES contains types of notifications.
    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 addNotifications we combine existing notifications with new ones using Lodash, and take only the last 5, which will be shown.

    We need some 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('');
            $(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 notifications on Github.

    Finally, some helper functions for creating notification strings.

    //...
    // Сделать строку уведомления
    function makeNotification(notification) {
        var to = routeNotification(notification);
        var notificationText = makeNotificationText(notification);
        return '
  • ' + notificationText + '
  • '; } // получить маршрут уведомления в зависимости от его типа function routeNotification(notification) { var to = '?read=' + notification.id; if(notification.type === NOTIFICATION_TYPES.follow) { to = 'users' + to; } return "https://habr.com/" + to; } // получить текст уведомления в зависимости от его типа function makeNotificationText(notification) { var text = ''; if(notification.type === NOTIFICATION_TYPES.follow) { const name = notification.data.follower_name; text += '' + name + ' 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 posts 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 AppPost;
    use AppUser;
    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 AppObservers;
    use AppNotificationsNewPost;
    use AppPost;
    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 AppObserversPostObserver;
    use AppPost;
    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 "https://habr.com/" + to;
    }
    function makeNotificationText(notification) {
        var text = '';
        if(notification.type === NOTIFICATION_TYPES.follow) {
            const name = notification.data.follower_name;
            text += `${name} followed you`;
        } else if(notification.type === NOTIFICATION_TYPES.newPost) {
            const name = notification.data.following_name;
            text += `${name} 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.

    Sign up for a free Pusher account at pusher.com and create a new application.

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

    Set your account settings in the configuration file broadcasting:

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

    Then we will register AppProvidersBroadcastServiceProvider in the array providers.

    // ...
    'providers' => [
        // ...
        AppProvidersBroadcastServiceProvider
        //...
    ],
    //...

    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.js and 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.

    Output

    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 (e-mail, 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 status of users.

    Please see their documentation (Pusher docs, Pusher tutorials, Laravel docs) to find out 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.


    Similar Posts

    Leave a Reply

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