Create web notifications using Laravel and Pusher Channels

Introduction

NOTE: As of July 2020 Pusher made web notifications available on the Beams API. Find out more about getting started quickly with Beams for Web.

When building web applications, it is not uncommon to find some sort of in-app notification system that will notify you instantly when someone carries out an action related to you or your account. On Facebook, you will be notified when someone likes your status, or when someone comments on your profile. We will replicate this feature by creating a web notifications system using Laravel and Pusher Channels.

What we would be building

After this tutorial we would demonstrate how we can have a small web application show notifications using Laravel and Pusher. It would be similar to how websites like Facebook show notifications. Here is a preview of what we would be building:

how-to-create-web-notifications-using-laravel-and-pusher

Setting up your Pusher application

Create a free sandbox Pusher account or sign in. Then set up your application as seen in the screenshot below.

how-to-create-web-notifications-using-laravel-and-pusher-1

Setting up your Laravel application

You can create a new Laravel application by running the command below in your terminal:

laravel new laravel-web-notifications

After that, we will need to install the Pusher PHP SDK, you can do this using Composer by running the command below:

composer require pusher/pusher-php-server

When Composer is done, we will need to configure Laravel to use Pusher as its broadcast driver, to do this, open the .env file that is in the root directory of your Laravel installation. Update the values to correspond with the configuration below:

1PUSHER_APP_ID=322700
2BROADCAST_DRIVER=pusher
3
4// Get the credentials from your pusher dashboard
5PUSHER_APP_ID=XXXXX
6PUSHER_APP_KEY=XXXXXXX
7PUSHER_APP_SECRET=XXXXXXX

IMPORTANT: If you’re using the EU or AP Cluster, make sure to update the options array in your config/broadcasting.php config since Laravel defaults to using the US Server. You can use all the options the Pusher PHP Library supports.

Open config/app.php and uncomment the App\Providers\BroadcastServiceProvider::class .

Creating our Laravel and Pusher application

Now that we are done with configuration, let us create our application. First we would create an Event class that would broadcast to Pusher from our Laravel application. Events can be fired from anywhere in the application.

php artisan make:event StatusLiked

This will create a new StatusLiked class in the app/Events directory. Open the contents of the file and update to the following below:

1<?php
2
3namespace App\Events;
4
5use Illuminate\Queue\SerializesModels;
6use Illuminate\Foundation\Events\Dispatchable;
7use Illuminate\Broadcasting\InteractsWithSockets;
8use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
9
10class StatusLiked implements ShouldBroadcast
11{
12    use Dispatchable, InteractsWithSockets, SerializesModels;
13
14    public $username;
15
16    public $message;
17
18    /**
19     * Create a new event instance.
20     *
21     * @return void
22     */
23    public function __construct($username)
24    {
25        $this->username = $username;
26        $this->message  = "{$username} liked your status";
27    }
28
29    /**
30     * Get the channels the event should broadcast on.
31     *
32     * @return Channel|array
33     */
34    public function broadcastOn()
35    {
36        return ['status-liked'];
37    }
38}

Above, we have implemented the ShouldBroadcast interface and this tells Laravel that this event should be broadcasted using whatever driver we have set in the configuration file.

We also have a constructor that accepts two parameters, username and verb. We will get back to this later on. We assigned these variables to class properties named the same way. It is important to set the visibility of the properties to public; if you don't, the property will be ignored.

Lastly, we set the channel name to broadcast on.

Creating the application views

We will keep it simple and create a single view where you can see a navigation bar with a notification icon. The icon will be updated when new notifications are available without the need to refresh the page. The notifications are ephemeral in this tutorial by design; you can extend the functionality and make it last longer after the page reloads if you so desire.

Open the welcome.blade.php file and replace it with the HTML below.

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4    <meta charset="utf-8">
5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
6    <meta name="viewport" content="width=device-width, initial-scale=1">
7    <title>Demo Application</title>
8    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
9    <link rel="stylesheet" type="text/css" href="/css/bootstrap-notifications.min.css">
10    <!--[if lt IE 9]>
11      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
12      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
13    <![endif]-->
14  </head>
15  <body>
16    <nav class="navbar navbar-inverse">
17      <div class="container-fluid">
18        <div class="navbar-header">
19          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-9" aria-expanded="false">
20            <span class="sr-only">Toggle navigation</span>
21            <span class="icon-bar"></span>
22            <span class="icon-bar"></span>
23            <span class="icon-bar"></span>
24          </button>
25          <a class="navbar-brand" href="#">Demo App</a>
26        </div>
27
28        <div class="collapse navbar-collapse">
29          <ul class="nav navbar-nav">
30            <li class="dropdown dropdown-notifications">
31              <a href="#notifications-panel" class="dropdown-toggle" data-toggle="dropdown">
32                <i data-count="0" class="glyphicon glyphicon-bell notification-icon"></i>
33              </a>
34
35              <div class="dropdown-container">
36                <div class="dropdown-toolbar">
37                  <div class="dropdown-toolbar-actions">
38                    <a href="#">Mark all as read</a>
39                  </div>
40                  <h3 class="dropdown-toolbar-title">Notifications (<span class="notif-count">0</span>)</h3>
41                </div>
42                <ul class="dropdown-menu">
43                </ul>
44                <div class="dropdown-footer text-center">
45                  <a href="#">View All</a>
46                </div>
47              </div>
48            </li>
49            <li><a href="#">Timeline</a></li>
50            <li><a href="#">Friends</a></li>
51          </ul>
52        </div>
53      </div>
54    </nav>
55
56    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
57    <script src="//js.pusher.com/3.1/pusher.min.js"></script>
58    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
59
60    <script type="text/javascript">
61      var notificationsWrapper   = $('.dropdown-notifications');
62      var notificationsToggle    = notificationsWrapper.find('a[data-toggle]');
63      var notificationsCountElem = notificationsToggle.find('i[data-count]');
64      var notificationsCount     = parseInt(notificationsCountElem.data('count'));
65      var notifications          = notificationsWrapper.find('ul.dropdown-menu');
66
67      if (notificationsCount <= 0) {
68        notificationsWrapper.hide();
69      }
70
71      // Enable pusher logging - don't include this in production
72      // Pusher.logToConsole = true;
73
74      var pusher = new Pusher('API_KEY_HERE', {
75        encrypted: true
76      });
77
78      // Subscribe to the channel we specified in our Laravel Event
79      var channel = pusher.subscribe('status-liked');
80
81      // Bind a function to a Event (the full Laravel class)
82      channel.bind('App\\Events\\StatusLiked', function(data) {
83        var existingNotifications = notifications.html();
84        var avatar = Math.floor(Math.random() * (71 - 20 + 1)) + 20;
85        var newNotificationHtml = `
86          <li class="notification active">
87              <div class="media">
88                <div class="media-left">
89                  <div class="media-object">
90                    <img src="https://api.adorable.io/avatars/71/`+avatar+`.png" class="img-circle" alt="50x50" style="width: 50px; height: 50px;">
91                  </div>
92                </div>
93                <div class="media-body">
94                  <strong class="notification-title">`+data.message+`</strong>
95                  <!--p class="notification-desc">Extra description can go here</p-->
96                  <div class="notification-meta">
97                    <small class="timestamp">about a minute ago</small>
98                  </div>
99                </div>
100              </div>
101          </li>
102        `;
103        notifications.html(newNotificationHtml + existingNotifications);
104
105        notificationsCount += 1;
106        notificationsCountElem.attr('data-count', notificationsCount);
107        notificationsWrapper.find('.notif-count').text(notificationsCount);
108        notificationsWrapper.show();
109      });
110    </script>
111  </body>
112</html>

This is mostly a lot of Bootstrap noise so we will isolate the important parts, mostly Javascript. We include the Pusher javascript library, and then we added the javascript block that does the magic. Let us look at some snippets of the javascript block:

1// Enable pusher logging - don't include this in production
2// Pusher.logToConsole = true;
3
4// Initiate the Pusher JS library
5var pusher = new Pusher('API_KEY_HERE', {
6    encrypted: true
7});
8
9// Subscribe to the channel we specified in our Laravel Event
10var channel = pusher.subscribe('status-liked');
11
12// Bind a function to a Event (the full Laravel class)
13channel.bind('App\\Events\\StatusLiked', function(data) {
14    // this is called when the event notification is received...
15});

PROTIP: By default, Laravel will broadcast the event using the event's class name. However, you may customize the broadcast name by defining a broadcast as method on the event:

1public function broadcastAs() {
2
3 return 'event-name';
4
5 }

The code above just initializes the Pusher JS library and subscribes to a channel. It then sets a callback to call when the event broadcasted is received on that channel.

Testing our set up

Finally to test our set up, we will create a route that manually calls the event. If everything works as expected, we will get a new notification anytime we hit the route. Let's add the new route:

1Route::get('test', function () {
2    event(new App\Events\StatusLiked('Someone'));
3    return "Event has been sent!";
4});

Now we can start a PHP server using Laravel so we can test our code to see if it works.

$ php artisan serve

Conclusion

In this article we have been able to leverage the power of Pusher to create a modern web notifications system and it was very easy. This is just scratching the surface of what can be really done using Pusher. The example was just to show you the possibilities.

The code is available on GitHub, you can star, fork and play around with it.