Just like you build your mailing list, now the time has been changed, push notification subscription list also a play crucial role and its important channel to keep customer traction under control. Push messaging provides an effective & easy way to keep the customer engaged.

In Guide, I'll explain in a very easy manner as much as possible with bare minimum source code as well, to how to integrate web push notification in any of your websites without many server-side changes, without any third party service.

You can integrate client-side code and use our nodejs server code as standalone separate service for sending push notificationsto your users, without making things within your server.

Let's get started.

Preparation

  • This guide will be using an express-js and node-js server to serve sample & admin HTML files and to send push notifications to users, you can use your choice of the web server and follow a similar approach.

  • We'll store push notifications subscription tokens on the server in a MySQL database, to later send them notifications using our same web server.

  • We'll need to generate public-private RSA key pairs in order to start using push notification feature.
    You can use https://web-push-codelab.glitch.me/ and save public-private keys for later use in this guide.

  • To make everything simple, we'll remove all unnecessary stuff from HTML and JS files so you can use them in your way in your app however you would like.

Service Worker Registrations

Push notifications are delivered to service worker in the browser, so we'll first register our service worker to the browser.
Service Work is a script which runs in the background in the browser in a separate thread and browser launch this whenever needed, it dont requires an active web page or active user on site.

Let's make a service worker file on the server called sw.js we'll discuss later about this, how it handles the actual raw data sent by the server and create a notification in the browser.

Let's create sw.js for now on server.

/sw.js

var url=null;

self.addEventListener('push', function(event) {
    var data=JSON.parse(event.data.text());
    if (data){
        if (data.url)
            data.notification.requireInteraction= true;
        if (data.url)
            url=data.url;
        event.waitUntil(self.registration.showNotification(data.title, data.notification));
    }
});
self.addEventListener('notificationclick', function(event) {
    if (url) {
        let page_open=url;
        url=null;
        //https://stackoverflow.com/a/39457287
        event.notification.close(); // Android needs explicit close.
        event.waitUntil(
            clients.matchAll({type: 'window'}).then(windowClients => {
                // Check if there is already a window/tab open with the target URL
                for (var i = 0; i < windowClients.length; i++) {
                    var client = windowClients[i];
                    // If so, just focus it.
                    if (client.url === page_open && 'focus' in client) {
                        return client.focus();
                    }
                }
                // If not, then open the target URL in a new window/tab.
                if (clients.openWindow) {
                    return clients.openWindow(page_open);
                }
            })
        );
    }
});

Let's register sw.js in the browser with following code.

<script>
    var isPushNotificationSupported=function(){
        return 'serviceWorker' in navigator && 'PushManager' in window;
    };
    if(isPushNotificationSupported()){
        navigator.serviceWorker.register('/sw.js').then(function(sw) {
            
            console.log('Service Worker is registered', sw);
            
        });
    }
</script>

Now as we've got our service worker instance, we can now continue to our next step about push notification subscription.

Push Notification Manager to Subscribe

Capturing Subscription Flow

In the browser which support Push Notification they have pushManager object in the window object, which is used to manage, subscribe,
revoke subscriptions.

We'll need now need our public key here for the subscription, which we've saved earlier.

Lets call pushManager to create a the subscription and pass the subscription to our saveSubscription function to save it further to our server in next step.

<script>
    
    var APP_PUBLIC_KEY='YOUR_PUBLIC_KEY';
    // REPLACE WITH YOUR PUBLIC KEY
        
        
    var isPushNotificationSupported=function(){
        return 'serviceWorker' in navigator && 'PushManager' in window;
    };
    var urlB64ToUint8Array=function(base64String) {
        var padding = '='.repeat((4 - base64String.length % 4) % 4);
        var base64 = (base64String + padding)
            .replace(/\-/g, '+')
            .replace(/_/g, '/');

        var rawData = window.atob(base64);
        var outputArray = new Uint8Array(rawData.length);

        for (var i = 0; i < rawData.length; ++i) {
            outputArray[i] = rawData.charCodeAt(i);
        }
        return outputArray;
    };
    
    if(isPushNotificationSupported()){
        navigator.serviceWorker.register('/sw.js').then(function(sw) {
            console.log('Service Worker is registered', sw);
            
            // Get the initial subscription value
            sw.pushManager.getSubscription()
                .then(function(subscription) {

                    if (subscription !== null) {
                        console.log('User is already subscribed.');
                        // depending on your application logic you can use this current subscription value again.
                    } else {
                        sw.pushManager.subscribe({
                            userVisibleOnly: true,
                            applicationServerKey: urlB64ToUint8Array(APP_PUBLIC_KEY)
                        }).then(function(subscription) {
                            
                            
                            console.log('User is subscribed now.');
                            // saveSubscription is will save token to server
                            saveSubscription(JSON.stringify(subscription),function (success) {
                                if (!success){
                                    // we were not able to save it, so lets unsubscribe it. so we'll get new subscription next time
                                    sw.pushManager.unsubscribe();
                                }
                            });
                            
                            
                        }).catch(function(err) {
                            console.error('Failed to subscribe the user: ', err);
                        });
    
                    }
                });   
        });
    }
</script>

Save Push Subscription

Now here we've got our subscription token and we'll now need to send subscription token to our server, so later we can send notifications to clients.

In this guide, we are using nodejs express server for saving token in MySQL database, but you can use other types of the server as well like PHP based web server or you can save token to any other database as well.

so here is saveSubscription which we've used in above code.

<script>
    var saveSubscription=function(subscription,callback){
        // send this value to your server..
        // upon successfully message from server, you can use callback(true)
        // or callback(false) if server is not able to save the subscription
        
        var http = new XMLHttpRequest();
        http.open('POST', '/subscribe', true);
        http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        
        http.onreadystatechange = function() {
            if(http.readyState == 4) {
                if (http.status == 200 && http.responseText=='OK')
                    callback(true);
                else callback(false);
            }
        };
        http.send('subscription='+encodeURIComponent(subscription));
    }
</script>

Now let's handle this on the server side, and lets first create a table in MySQL database called push_subscribers.

CREATE TABLE `push_subscribers` (
  `id` VARCHAR(64) NOT NULL,
  `subscription` TEXT
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

Below is our code for our nodejs express server which will be used to save subscription tokens.

server.js

const express = require('express');
const fs=require('fs');
const crypto=require('crypto');
const app = express();
const port = 3000;


const mysql=require('mysql').createConnection({
    host     : 'MYSQL_SERVER_HOST',
    user     : 'MYSQL_SERVER_USER',
    password : 'MYSQL_SERVER_PASSWORD',
    database : 'MYSQL_SERVER_DATABASE'
});
mysql.connect();


app.use(express.static('web'));
app.use(express.urlencoded());

app.post('/subscribe', (req, res) => {
    // lets save all subscriptions in plain-text file.
    if(req.body.subscription){
        let subscription=req.body.subscription;
        let timezone=req.body.timezone;
    
        var id=crypto.createHmac('sha256', 'one_time_secret').update(subscription).digest('hex');
    
        mysql.query('SELECT id from push_subscribers WHERE  id=?',[id],  (error, results, fields) => {
            if (error || results.length)
                console.error(error || 'already subscribed') || res.send('ERROR');
            else
                mysql.query('INSERT INTO push_subscribers SET ?',{
                    id:id,
                    subscription:subscription
                },(error, result)=>{
                    if (error)
                        console.error(error) || res.send('ERROR');
                    else
                        res.send('OK');
            });
        });
    } else
        res.send('Invalid Parameters');
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}!`)
});

Send Notifications

Sending Web Push Notification Flow

Now as we've saved user's subscriptions in the database, we'll now ready start sending notifications to users.

In reality, we don't send notifications but the data, this data goes to our service worker which then create notification or can any perform any allowable actions based on that data, means we can use the same for other purposes as well.

To send push notification, we'll be using web-push library in nodejs, which support various types of subscriptions tokens.

Let's create a another method for sending notification and which will be called by a HTML file in our server admin/index.html, here we won't write code for admin.html which you can simply get from full code repository shared along with this article.

We'll also need to protect this /admin/* method using http basic authentication so only we can access that and send notifications.

 
// Lets protect our /admin/* path from unauthorized access only be accessible with valid username password
const auth = require('http-auth');

// admins.htpasswd is available (default username admin, password admin) in git repo  or you can generate yours http://www.htaccesstools.com/htpasswd-generator/
app.use('/admin/*',auth.connect(auth.basic({realm:"Admin Area"},(username, password, callback)=>{
    // Custom authentication
    // Use callback(error) if you want to throw async error.
    callback(
        username === 'admin' &&
        password === 'admin'
        // check full code repo we've validated username & passwords with proper hash from config files
    );
})));

// Confgiure Web Push Library
const webpush = require('web-push');
webpush.setVapidDetails(
    // PUT YOUR EMAIL HERE
    'mailto:[email protected]',
    // HERE IS YOUR PUBLIC KEY
    'YOUR_PUBLIC_KEY',
    // HERE IS YOUR PRIVATE KEY
    'YOUR_PRIVATE_KEY'
);

const async = require('async');
app.post('/admin/send', (req, res) => {
    if(req.body.title && req.body.notification){
        
        // Our admin.html have feature to send preview before sending it to all
        // You can skip this and jump to next condition if you want
        if(req.body.preview)
            webpush.sendNotification(JSON.parse(req.body.preview), JSON.stringify({
                title:req.body.title,
                notification:req.body.notification,
                url:req.body.url,
            })).then((r)=>{
                res.send('Sending Notifications to you.');
            }).catch((ee)=>{
                res.send('ERROR: '+ee.toString());
                console.error(ee);
            });
        else
            mysql.query('SELECT id,subscription from push_subscribers ', function (error, results, fields) {
                if (error || results.length==0)
                    console.error(error) && res.send(error?"DB ERROR":'NO SUBSCRIBERS');
                else {
                    res.send('Sending Notifications to '+(results.length)+' subscribers.');
                    
                    // now lest send them all in background
                    let success=0,failed=0;

                    // Lets send notification one by one, not in parallel fashion
                    // req.body.notification is another object with sub properties of notification 
                    // https://developer.mozilla.org/en-US/docs/Web/API/notification#Instance_properties
                    
                    async.eachSeries(results,(item, callback)=>{
                        webpush.sendNotification(JSON.parse(item.subscription), JSON.stringify({
                            title:req.body.title,
                            notification:req.body.notification,
                            url:req.body.url,
                        })).then((r)=>{
                            success++;
                        }).catch((ee)=>{
                            failed++;
                            console.log('ERROR: '+ee.toString());
                            console.error(ee);
                        }).finally(()=>{
                            callback(null,true);
                        });
                    },()=>{
                        console.log("PUSH NOTIFICATIONS SENT: success:"+success+' failed:'+failed);
                    });
                }
            });
    } else res.send('Invalid Parameters');
});

Now server side work is mostly done, you are ready to send push notifications now. and object we've send is some like this

{
    title: "Notification Title",
    notification: {
        // https://developer.mozilla.org/en-US/docs/Web/API/notification#Instance_properties
    },
    url: "" // optional
}

Full Source Code

You can download the full source code from here, don't forget that giving it a star or feedback.

https://github.com/dev-mraj/web-push-notification-integration

git clone https://github.com/dev-mraj/web-push-notification-integration
cd web-push-notification-sample
npm install

# Edit config.json and put db details and notiifcaiton public private keys
node server.js

# Main Page http://localhost:3000
# Admin Page  http://localhost:3000/admin

Other Resources

https://developers.google.com/web/fundamentals/codelabs/push-notifications/
https://developers.google.com/web/fundamentals/push-notifications/
https://developers.google.com/web/fundamentals/push-notifications/how-push-works

Support

If you found the article & code useful, consider sharing with online web developer communities or social media.

Also, let me the feedback for same on comments below about what I've missed or what more to improve.