Web Push Notification Full Stack Application With Node Js Restful API
Tags: node, mongodb, pushnotification, webpush
Published: 2018-09-25 08:24:18

---
title: Web Push Notification Full Stack Application With Node Js Restful API
published: true
description: Web push notification web service is used for broadcasting short messages to your subscribed users, no need to third party services for web push notification.it is totally independent own managed web push notification web service.
tags: Nodejs, MongoDB, Push Notification , Web Push 
---
Let me tell you first, why **Web Push Notification** is essential for any web application.
> It's helps in broadcasting short messages to the subscriber of the web application and as well as help to support in user engagement with your application to notify the every subscribers, It is really hard to any organisation send email notification for a short messaging event loop or broadcast messages like offers and updates of short messages and it also increases the cost of server operations so the solution is **Web Push Notification** .it help your the maximum post reachability and getting more consumer engagement on web application.

let me tell you how it's works.
![web-push-how-working.png](https://cdn.filestackcontent.com/3iE3ihH2TLqI64zeNDsF)

service worker is main key ingrediant and knight of this feature which install in client broswer and run indenpendently as application after intalling in browser as service worker which regularly a send a query to the provider server and ask for any new event happening and than respond to the client if any new event is happening in the server it popup a message like
![popup-notify.png](https://cdn.filestackcontent.com/632p2e4RROJCAUPdGPKg)

click on allow button service worker start to install in client browser and send a promise request for subscriber with **Public VAPID key** and check on server for whether user is already subscribe notification or not if it's already subscribe it sent back request with false statement otherwise server sent a true request.
and done that's it.
Now let's Comes to the Coding section and how to implement this feature in your application without using third party paid services and use as long as you.
## **Step 1**
Need Prequest which are listed below if you don't have in your System.
**Prequiests:**
1. IDE [Visual Studio Code](https://visualstudio.microsoft.com/)
2. [Nodejs](https://nodejs.org/en/download/)
3. [Git](https://git-scm.com/) 
4. [MongoDB](https://www.mongodb.com/download-center?jmp=nav#charts)
5. [Postman](https://www.getpostman.com/apps)
 
 Now Let's Beigin with next Step
 
 ## **Step 2**
 
 open your IDE [Visual Studio Code](https://visualstudio.microsoft.com/)
 and than run command in integrated terminal with your IDE
 ```
git init
```
than add the all fields or skip as you want
and than run command again to intall all the dependacy with
```
npm install express web-push body-parser mongoose q --save
```
![git-init-commands.png](https://cdn.filestackcontent.com/GPPnJskQTGhfljoNHSEn)
and hit the Enter and wait for install all the dependency will install correclty in your project  than run again run command for creat new application running file
in same project folder by
```
touch server.js
```

![add-new-file-app.png](https://cdn.filestackcontent.com/Vp8V4BfDQV67JLwYgoCD)
and again need to create three folders in same project directory by commands as below
```
mkdir config
cd config 
touch keys.js
touch keys_prod.js
touch keys_dev.js
```
![creat-config-dir.png](https://cdn.filestackcontent.com/fgYHnCfRAWCT59r8yR2f)

```
mkdir model
cd model
touch subscribers_model.js
```
![creat-model-dir.png](https://cdn.filestackcontent.com/zcdVJawpQ1udam8tvJE7)

```
mkdir router
cd router
touch push.js
touch subscriber.js
touch index.js
```
![creat-router-dir.png](https://cdn.filestackcontent.com/Za6RRQStQMqtqALHMkto)

now all the essential folders and file are created and in this project we move to next coding parts in next step.

 ## **Step 3**
 The file structure of this project is as below
 ```
|
|
|________________________./config
|                         |
|                         |____keys_prod.js
|                         |____keys_dev.js
|                         |____keys.js
|
|________________________./public
|                         |
|                         |____index.html
|                         |____sw.js
|                         |____app.js
|
|________________________./model
|                         |
|                         |____subscribers_model.js
|
|________________________./router
|                         |
|                         |____push.js
|                         |____subscribe.js
|
|___________________________server.js
```

 now start with create database model for mongodb database. so now i am using **Mongoose** ODM ORM library for **MongoDB** which is already installed in project
 ```
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const SubscriberSchema = new Schema({
    endpoint: String,
    keys: Schema.Types.Mixed,
    createDate: {
        type: Date,
        default: Date.now
    }
});

mongoose.model('subscribers', SubscriberSchema, 'subscribers');
```
so now let's come to the config file 
```
cd config
``` 
and than open the keys.js file which is already created in this folder
```
if (process.env.NODE_ENV === 'production') {
    module.exports = require('./keys_prod');
} else {
    module.exports = require('./keys_dev');
}
``` 
and update your keys.js file with this code , actually this code provide smart switch database authentication address between production and development application.
Before update the keys_prod.js and keys_dev.js file generate the VAPID keys for client device browser and between the server running application.
by using this command
```
./node_modules/.bin/web-push generate-vapid-keys
```
you will see a two keys are generated one is private and another one is public key
 which is show in below.
 ![create-vapid-key.png](https://cdn.filestackcontent.com/Dw6GP2JWQhuPne1Iu074)
 copy both the keys and paste in to the keys_dev.js or on production enviroment server config.
 ```
module.exports = {
//i used mlab database for fast and realiable pace development enviroment
    mongoURI: 'mongodb://web-push:webpush123@ds213053.mlab.com:13053/web-push',
    privateKey: 'ayTIBl3f0gcI-koFq-ZXPxSR4qicC0GcMNHA1dpHaj0' || process.env.VAPID_PRIVATE_KEY,
    publicKey: 'BK3Q7j8fcGFws03RiU5XakzDJ7KGEiRhdIX2H5U8eNmhhkdHT_j0_SD09KL96aFZOH_bsjr3uRuQPTd77SRP3DI' || process.env.VAPID_PUBLIC_KEY
}

```
**process.env.VAPID_PUBLIC_KEY** or **process.env.VAPID_PRIVATE_KEY** understand as production server running environment configuration.

> **Note:** Please Make sure you always use or generate your own VAPID keys for your application for more secure your application and make sure your private keys is not expose on the web.

so now all the important application structure setting is done now start coding in **server.js** which is exist on top of the project folder

```
const express = require('express');
const path = require('path');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');

require('./model/subscribers_model');

// Load Routes
const index = require('./router');

// subscriber route load push
const push = require('./router/push');

// subscriber route load
const subscribe = require('./router/subscribe');
// Load Keys
const keys = require('./config/keys');
//Handlebars Helpers

mongoose.Promise = global.Promise;

// Mongoose Connect
mongoose.connect(keys.mongoURI, {
        useMongoClient: true
    })
    .then(() => console.log('MongoDB Connected'))
    .catch(err => console.log(err));

//Create Express middleware
const app = express();
app.set('trust proxy', true);
// parse application/json
app.use(bodyParser.json());
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({
    extended: true
}));

// Set static folder
app.use(express.static(path.join(__dirname, 'public')));
// app.set('views', __dirname + '/public/js');

// Set global vars
app.use((req, res, next) => {
    res.locals.user = req.user || null;
    next();
});



// Use Routes

app.use('/', index);
app.use('/subscribe', subscribe);
app.use('/push', push);


// catch 404 and forward to error handler
app.use(function (req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function (err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


const port = process.env.PORT || 3000;

app.listen(port, () => {
    console.log(`Server started on port ${port}`);
});
```

and now come to the folder **router** first start with subscribe.js which is already created by command. open this file in new tab and than paste this code in your **subscribe.js** file
```
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const Subscription = mongoose.model('subscribers');

//Post route of subscribe url is as http://host:3000/subscribe
router.post('/', (req, res) => {
    const subscriptionModel = new Subscription(req.body);
    subscriptionModel.save((err, subscription) => {
        if (err) {
            console.error(`Error occurred while saving subscription. Err: ${err}`);
            res.status(500).json({
                error: 'Technical error occurred'
            });
        } else {
            res.json({
                data: 'Subscription saved.'
            });
        }
    });
});

// fixed the error get request for this route with a meaningful callback
router.get('/', (req, res) => {
            res.json({
                data: 'Invalid Request Bad'
            });
});
module.exports = router;
```
save the changes and move to next file **push.js** and paste this code in your already created **push.js** file by command line
```
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const Subscription = mongoose.model('subscribers');
const q = require('q');
const webPush = require('web-push');
const keys = require('./../config/keys');
//Post route of push url is as http://host:3000/push
router.post('/', (req, res) => {
    const payload = {
        title: req.body.title,
        message: req.body.message,
        url: req.body.url,
        ttl: req.body.ttl,
        icon: req.body.icon,
        image: req.body.image,
        badge: req.body.badge,
        tag: req.body.tag
    };

    Subscription.find({}, (err, subscriptions) => {
        if (err) {
            console.error(`Error occurred while getting subscriptions`);
            res.status(500).json({
                error: 'Technical error occurred'
            });
        } else {
            let parallelSubscriptionCalls = subscriptions.map((subscription) => {
                return new Promise((resolve, reject) => {
                    const pushSubscription = {
                        endpoint: subscription.endpoint,
                        keys: {
                            p256dh: subscription.keys.p256dh,
                            auth: subscription.keys.auth
                        }
                    };

                    const pushPayload = JSON.stringify(payload);
                    const pushOptions = {
                        vapidDetails: {
                            subject: "http://example.com",
                            privateKey: keys.privateKey,
                            publicKey: keys.publicKey
                        },
                        TTL: payload.ttl,
                        headers: {}
                    };
                    webPush.sendNotification(
                        pushSubscription,
                        pushPayload,
                        pushOptions
                    ).then((value) => {
                        resolve({
                            status: true,
                            endpoint: subscription.endpoint,
                            data: value
                        });
                    }).catch((err) => {
                        reject({
                            status: false,
                            endpoint: subscription.endpoint,
                            data: err
                        });
                    });
                });
            });
            q.allSettled(parallelSubscriptionCalls).then((pushResults) => {
                console.info(pushResults);
            });
            res.json({
                data: 'Push triggered'
            });
        }
    });
});

// fixed the error get request for this route with a meaningful callback
router.get('/', (req, res) => {
    res.json({
        data: 'Invalid Request Bad'
    });
});
module.exports = router;
```
again make sure save this code changes in your **push.js** file with this code now again move to the **index.js** file and update the code with this below code
```
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
    res.locals.metaTags = {
        title: 'web-push-api',
        description: 'Web Push Notification Full Stack Application With Node Js Restful API',
        keywords: 'Web Push Notification Full Stack Application With Node Js Restful API',
        generator: '0.0.0.1',
        author: 'Saurabh Kashyap'
    };
    res.json({
        status: 'ok',
        message: 'Server is running'
    });
});

module.exports = router;
```
and save the changes in server.js file with above code in **server.js** file and run command hit this run command
```
node server.js
```
please make sure you will see these messages after hitting this command.
![server-running-command.jpg](https://cdn.filestackcontent.com/Cr52K8NiQPOXAv6jOHSe)

press again close the application after checking your application is running correct.
so now server side running application code is done.
Now Let's Begin with next Step

## **Step 4**

creat a new folder in with public name and create file with these commands as below

```
mkdir public
cd public
touch index.html
touch sw.js
touch app.js
```

  ![create-public-dir.png](https://cdn.filestackcontent.com/FMH7LgpjQoiL2n5wFPtc)
 ![creat-public-dir-files.png](https://cdn.filestackcontent.com/tBj8EHNRZ5pYPfWDQhbA)
 
 now lets beign the basic html code in **index.html** file
 ```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Web-Push Application with Restful Api</title>
</head>
<body>
    <h1>This is a web-push notification example</h1>
    <!-- call service worker for register and send subscribe request to the server with javascript -->
<script src="app.js"></script>
</body>
</html>
```
save this code and move to next file **app.js** where service worker browser check and register service worker in browser and also send a ajax request to the application API **http://host:3000/subscribe** for subscribe the service in client browser.
```
let isSubscribed = false;
let swRegistration = null;
let applicationKey = "put_your_public_key_here";


// Url Encription
function urlB64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

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

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

// Installing service worker
if ('serviceWorker' in navigator && 'PushManager' in window) {
    console.log('Service Worker and Push is supported');

    navigator.serviceWorker.register('sw.js')
        .then(function (swReg) {
            console.log('service worker registered');

            swRegistration = swReg;

            swRegistration.pushManager.getSubscription()
                .then(function (subscription) {
                    isSubscribed = !(subscription === null);

                    if (isSubscribed) {
                        console.log('User is subscribed');
                    } else {
                        swRegistration.pushManager.subscribe({
                                userVisibleOnly: true,
                                applicationServerKey: urlB64ToUint8Array(applicationKey)
                            })
                            .then(function (subscription) {
                                console.log(subscription);
                                console.log('User is subscribed');

                                saveSubscription(subscription);

                                isSubscribed = true;
                            })
                            .catch(function (err) {
                                console.log('Failed to subscribe user: ', err);
                            })
                    }
                })
        })
        .catch(function (error) {
            console.error('Service Worker Error', error);
        });
} else {
    console.warn('Push messaging is not supported');
}

// Send request to database for add new subscriber
function saveSubscription(subscription) {
    let xmlHttp = new XMLHttpRequest();
    //put here API address
    xmlHttp.open("POST", "/subscribe");
    xmlHttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xmlHttp.onreadystatechange = function () {
        if (xmlHttp.readyState != 4) return;
        if (xmlHttp.status != 200 && xmlHttp.status != 304) {
            console.log('HTTP error ' + xmlHttp.status, null);
        } else {
            console.log("User subscribed to server");
        }
    };

    xmlHttp.send(JSON.stringify(subscription));
}
```
and now save the all files and start coding for a service worker let's begin now


```
let notificationUrl = '';
//notification registered feature for getting update automatically from server api
self.addEventListener('push', function (event) {
    console.log('Push received: ', event);
    let _data = event.data ? JSON.parse(event.data.text()) : {};
    notificationUrl = _data.url;
    event.waitUntil(
        self.registration.showNotification(_data.title, {
            body: _data.message,
            icon: _data.icon,
            tag: _data.tag
        })
    );
});

//notification url redirect event click
self.addEventListener('notificationclick', function (event) {
    event.notification.close();

    event.waitUntil(
        clients.matchAll({
            type: "window"
        })
        .then(function (clientList) {
            if (clients.openWindow) {
                return clients.openWindow(notificationUrl);
            }
        })
    );
});
```
nad save the all code . YEAH..!! done. so now we have to check wheater is it working or not. so again run command in  your terminal 
```
node server.js
```
open **url: http://localhot:3000** in your browser now 
![puh-popup.png](https://cdn.filestackcontent.com/G7sZ1AuSyOF9DHyvcA2f)

after click on allo you see message like in your browser console