Integrating Push Notifications in your Node/React Web App

A walk through of integrating the Push API through the use of Service Workers into your Node.js back end and your React.js front end

Let’s start at the top: what are push notifications and why do I want to use them?

Push notifications are the use of the Notification API in conjunction with data sent over the Push API. They are one of the many features at the heart of giving Progressive Web Apps a truly competitive experience to their Native App counterparts. Much like Sockets, they allow a server to send data to the client without waiting for the client to initiate the transaction as is required over HTTP. Unlike Sockets, however, they allow the transfer of this data in the background while your app is closed. This is what allows a user to receive messages, event notifications, transaction updates, etc. without having to have your app open.

So how does push work?

In order for the client to receive a push in the background while your app is closed, your app must make use of service workers. A service worker is a Javascript file installed on the clients device that let’s the device know what files to cache and what events to listen for. This service worker is able to register a user’s device with the cloud service and is programmed with instructions on how to handle a push from the cloud. Whence the service worker is registered with the cloud it is programmed to send the received token back to your server which acts as an address to the end users device.

Handling the Push API in Node.js

In this workshop, we will be using web-push to handle the Push API. The first thing we’ll need to get set up, is to generate VAPID keys and attain a Google API key. To start with: npm i -g web-push, the global install will allow us to generate VAPID keys directly from the command line. Once installed we can simply run web-push generate-vapid-keys in the command line, and then copy/paste the resulting keys into a secrets.js file or into our deployed apps environmental variables. For the Google key you’ll need to go to the Google API Console, create a project, enable the Google Cloud Messaging API and then create an API key under the credentials tab.

The next step will be creating a new push router and importing it into our api router: app.use('/push', require('./push'). Our push router is where we’ll be handling all of our push logic and boilerplate, so let’s go ahead and get that all set up:

VAPID details are the way that clients are able to hold product owners accountable. If there were a bug negatively impacting the client, these VAPID details give the cloud service a way to make a claim. The VAPID keys are also used in conjunction with the keys provided by the user subscription to encrypt data sent through push. Read more about VAPID details here

Up next will be providing routes to post and delete subscriptions from and a simple script to send some test data on an interval. For the purpose of this tutorial we’ll just be storing the subscription in a variable, however in actual production you would likely store these details in a database along with other user details.

In the above example, we expect a registration to be posted in the body. The webpush.sendNotification function takes this entire subscription, which should include an endpoint to deliver the push to, and the keys to encrypt the data with. The second parameter taken is a string of data. This could be empty, a simple message, or, as is done above, a JSON string. The function has been set on an interval to allow our test to demonstrate that the push notifications are still received and displayed when the tab or window is closed. Above we’ve also included a delete route that stops the push interval and deletes the subscription object.

Wiring Up Our React Front End with the Push API

The front end is where a lot of the real magic happens with push. When a client visits our app, we want to install a service worker that has an event listener for push events. Going in depth on how to setup service workers goes a bit beyond the scope of this workshop, but if you are interested in learning more Mozilla has some great resources here. What we’re interested in is the push event listener:

As mentioned above, the data attached to the push event has to be a string. So the first thing we’re going to want to do is parse it back into an object. The showNotification function takes a title and an optional object as parameters.

Now that we have our event listeners in place we’ll want to supply our clients with a way to subscribe and unsubscribe. Let’s start by creating a new module to handle this in, if you are using react-redux this functionality could be handled in a subreducer and the subscribe/unsubscribe functions could take place in a thunk. For this though, we’ll just focus on the functionality of handling our subscriptions. The webpush module supplies us with some helpful boilerplate in their documentation to convert the URL safe base64 string to a Uint8Array:

Now that we’ve got some boilerplate let’s get onto these subscriptions!

In the above code we make sure that the service worker we’ve installed is ready, then we take the service worker registration and check if a pushManager is present, this is what will be handling the subscription and storing it for our client in the front end. We pass the pushManager’s subscribe function an object with our now converted public VAPID Key and an option to make sure that our notifications are always displayed. Then we finally send the subscription back to our server and viola! If you’ve been following along and incorporating this into an existing project, then this function just needs to be attached to a button, or to run when a page loads and it will be ready for demonstration! You should be able to close the tab with your Web App and still receive notifications.

But Oh No! Now we’re getting never ending notifications about our application testing with no end in sight! We better go ahead and give ourselves, and our users a way to unsubscribe:

The above code finds the subscription first and then checks if it exists or not. If it does exist, it unsubscribes, and then sends a delete request to our server thus ending our never ending stream of test data!