Web Push Notification with web-push | Angular & Node JS
Tags: webpush, pushnotification, angular, node
Published: 2022-02-06 16:16:13

Push notifications are a compelling way to engage users.

Push technology, or server push, is a style of Internet-based communication where the request for a given transaction is initiated by the publisher or central server. - Wikipedia

In this article, we will learn how to quickly add push notification in our Angular application with [Service Worker](https://developers.google.com/web/fundamentals/primers/service-workers).

## Service Worker 👷‍♂️
A service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction.
[Angular Service Worker](https://angular.io/guide/service-worker-intro)

## Prerequisites 📝
Basic knowledge of Angular & Node JS

So, If you are ready, Let's get started 🚀🚀🚀

![Let's Start](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4dff3hpqzr1mg8qfw94r.gif)

## Let’s Begin 🏁

### Step 1 : Create a server
Let's create a server directory inside our root directory.

```shell
.
└───server
```
Inside `/server`, run below command to initialize **npm**.

```shell
npm init -y
```
A `package.json` file will be generated for you.
```json
{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```
Now let's install `web-push` by running following command.

```shell
npm i web-push
```

Updated `package.json`
```json
{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "web-push": "^3.4.5"
  }
}

```

Amazing 👏👏👏

Let's create our server file `server/index.js`
```shell
server
    └───index.js
    └───package-lock.json
    └───package.json
    └───node_modules
```

Import `web-push` as below

```js
const webpush = require('web-push'); // new
```

To subscribe push messages, we need to pass **VAPID** keys.
We can generate **VAPID** keys as below.
```js
const webpush = require('web-push');

console.log(webpush.generateVAPIDKeys()); // new
```
Learn more about web push protocol [here](https://developers.google.com/web/fundamentals/push-notifications/web-push-protocol).

Let's run our server. It will print our keys in the console.
```shell
node .
```

Output
```shell
{
  publicKey: '<YOUR_PUBLIC_KEY>',
  privateKey: '<YOUR_PRIVATE_KEY>'
}
```
Now copy and put these keys in a variable. And remove `console.log` for generating keys.

```js
const webpush = require('web-push');

const vapidKeys = { // new
  publicKey: '<YOUR_PUBLIC_KEY>', // new
  privateKey: '<YOUR_PRIVATE_KEY>' // new
}; // new
```

Then create a variable called **subscription** as below.
```js
// get client subscription config from db
const subscription = {
    endpoint: '',
    expirationTime: null,
    keys: {
        auth: '',
        p256dh: '',
    },
};
```

**endpoint**: This contains a unique URL to a messaging server endpoint. This url is a public but unguessable endpoint to the Browser Push Service used by the application server to send push notifications to this subscription.

**expirationTime**: Some messages are time sensitive and don't need to be sent if a certain time interval has passed. This is useful in certain cases. For example, a message might contain an authentication code that expires after 1 minute.

**p256dh**: This is an encryption key that our server will use to encrypt the message, before sending it to the Push Service.

**auth**: This is an authentication secret, which is one of the inputs of the message content encryption process.

We'll get the subscription details from client. You can store that subscription config in DB and fetch the details here.

Now let's create **payload** for the notification.
```js
const payload = {
    notification: {
        title: 'Title',
        body: 'This is my body',
        icon: 'assets/icons/icon-384x384.png',
        actions: [
            { action: 'bar', title: 'Focus last' },
            { action: 'baz', title: 'Navigate last' },
        ],
        data: {
            onActionClick: {
                default: { operation: 'openWindow' },
                bar: {
                    operation: 'focusLastFocusedOrOpen',
                    url: '/signin',
                },
                baz: {
                    operation: 'navigateLastFocusedOrOpen',
                    url: '/signin',
                },
            },
        },
    },
};
```

The Angular service worker supports the following operations:

**openWindow** : Opens a new tab at the specified URL, which is resolved relative to the service worker scope.

**focusLastFocusedOrOpen** : Focuses the last focused client. If there is no client open, then it opens a new tab at the specified URL, which is resolved relative to the service worker scope.

**navigateLastFocusedOrOpen** : Focuses the last focused client and navigates it to the specified URL, which is resolved relative to the service worker scope. If there is no client open, then it opens a new tab at the specified URL.

Check different payloads [here](https://web-push-book.gauntface.com/display-a-notification/).
You can play with different types of notification [here](https://tests.peter.sh/notification-generator/).

Now add our third variable **options**.
```js
const options = {
    vapidDetails: {
        subject: 'mailto:example_email@example.com',
        publicKey: vapidKeys.publicKey,
        privateKey: vapidKeys.privateKey,
    },
    TTL: 60,
};
```

At last call `sendNotification()` method to send notification as below.

```js
// send notification
webpush.sendNotification(subscription, JSON.stringify(payload), options)
    .then((_) => {
        console.log('SENT!!!');
        console.log(_);
    })
    .catch((_) => {
        console.log(_);
    });
```

Here is our final code looks like.
```js
const webpush = require('web-push');

const vapidKeys = {
  publicKey: '<YOUR_PUBLIC_KEY>',
  privateKey: '<YOUR_PRIVATE_KEY>'
};

// get client subscription config from db
const subscription = {
    endpoint: '',
    expirationTime: null,
    keys: {
        auth: '',
        p256dh: '',
    },
};

const payload = {
    notification: {
        title: 'Title',
        body: 'This is my body',
        icon: 'assets/icons/icon-384x384.png',
        actions: [
            { action: 'bar', title: 'Focus last' },
            { action: 'baz', title: 'Navigate last' },
        ],
        data: {
            onActionClick: {
                default: { operation: 'openWindow' },
                bar: {
                    operation: 'focusLastFocusedOrOpen',
                    url: '/signin',
                },
                baz: {
                    operation: 'navigateLastFocusedOrOpen',
                    url: '/signin',
                },
            },
        },
    },
};

const options = {
    vapidDetails: {
        subject: 'mailto:example_email@example.com',
        publicKey: vapidKeys.publicKey,
        privateKey: vapidKeys.privateKey,
    },
    TTL: 60,
};

// send notification
webpush.sendNotification(subscription, JSON.stringify(payload), options)
    .then((_) => {
        console.log('SENT!!!');
        console.log(_);
    })
    .catch((_) => {
        console.log(_);
    });

```
Great work so far 💪💪💪

![Great Work](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0dgo6c7frtzkjuqbn9z.gif)

Keep this server code as it is for now.
Let's create our fronted.

### Step 2 : Create the client

Let's come back to our root directory and run below command to create an angular project `client`.
```shell
ng new client
```

Now inside `client`, run the below command to add all the necessary configurations for [PWA](https://web.dev/progressive-web-apps/) in our app.

```shell
ng add @angular/pwa
```

Go to `app.component.ts` and add `ngOnInit()` method as below.
```ts
export class AppComponent implements OnInit {
  title = 'client';

  ngOnInit() {}
}
```

Import `SwPush` from `@angular/service-worker` and add to the constructor.
```ts
import { SwPush } from "@angular/service-worker";

export class AppComponent implements OnInit{
  title = 'client';

  constructor(private _swPush: SwPush) {}

  ngOnInit() {}
}
```

Then create a method `requestSubscription()` which will request for notification permission and will give us the subscription object.
```ts
 requestSubscription = () => {
    if (!this._swPush.isEnabled) {
      console.log("Notification is not enabled.");
      return;
    }

    this._swPush.requestSubscription({
      serverPublicKey: '<VAPID_PUBLIC_KEY_FROM_BACKEND>'
    }).then((_) => {
      console.log(JSON.stringify(_));
    }).catch((_) => console.log);
  };
```

Call `requestSubscription()` method in `ngOnInit()`
```ts
  ngOnInit() {
    this.requestSubscription();
  }
```

Let's build our app to run our application with the Service Worker.
```shell
ng build
```

After build complete, go to `dist/client`, you will find a file named `ngsw-worker.js`. That's our service worker.

Now install `http-server` globally in your machine.
```shell
npm i -g http-server
```

After that go to `dist/client` in your terminal and run
```shell
http-server -p 8000
```

Now our project is running at `localhost:8000`.

When we'll open our app, it will ask us for the notification permission.

![Notification Permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufu1bbagmp26o6i4yz5a.png)

Isn't this amazing? 🤩🤩🤩

![Amazing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jl3t91pzv1onl4xi4l95.gif)

And if we **allow**, then in console we will get the subscription object.

![Subscription object](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oks78o2x5z5n9swle4qo.png)

Now you can call your own api to save this details in DB.

But here we will copy the subscription object generated in our client, and replace the **subscription** value in our server.

```js
const subscription = {
    endpoint:
        '<CLIENT_ENDPOINT>',
    expirationTime: null,
    keys: {
        p256dh: '<CLIENT_P256DH>',
        auth: '<CLIENT_AUTH>',
    },
};
```

Now in separate terminal go to `/server` directory and run
```shell
node .
```

You will immediately get your notification.

![Notification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/laql29dxyfley9r31tk4.png)


![Wow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5lxuyhesbdxr4brb5yp.gif)

Now you can play with the click events by clicking the action buttons and the notification itself.

## Conclusion 📋

Checkout web-push implementation for different backend technologies -
https://github.com/web-push-libs

Here is my GitHub link for this project - https://github.com/devsmranjan/web-push-notification-demo

Thank you for reading my article 🙂 . I hope you have learned something here.

Happy coding 👨‍💻👩‍💻

Thanks! Don't forget to give a ♥️ and follow :)