Using service workers, you can send push notifications to Chrome straight from your Node.js app. The excellent
web-push
npm module lets you send
push notifications without going through an intermediary service like PubNub. This article will walk you through setting
up a "Hello, World" example of web push notifications using a vanilla JavaScript
frontend and Express on the backend. The final result will look like what you
see below. The full source for this project is available on GitHub.
Credentials and Server Setup
In order to set up web push, you need to create a set of VAPID keys. VAPID keys identify who is sending the push notification. The web-push
npm
module can generate VAPID keys for you, so let's install web-push
along with some other dependencies and use web-push generate-vapid-keys
to create the keys.
$ npm install express@4.16.3 web-push@3.3.0 body-parser@1.18.2 express-static@1.2.5
+ express@4.16.3
+ web-push@3.3.0
+ body-parser@1.18.2
+ express-static@1.2.5
added 62 packages in 1.42s
$
$ ./node_modules/.bin/web-push generate-vapid-keys
=======================================
Public Key:
BOynOrGhgkj8Bfk4hsFENAQYbnqqLSigUUkCNaBsAmNuH6U9EWywR1JIdxBVQOPDbIuTaj0tVAQbczNLkC5zftw
Private Key:
<OMITTED>
=======================================
$
In order to support older browsers you may need to also get a GCM API key, but you don't need this in desktop Chrome 63 or higher.
Next, create a file called index.js
that will contain your server. You'll
need to require()
and configure the web-push module with your VAPID keys.
In the interest of simplicity, put the VAPID keys in the PUBLIC_VAPID_KEY
and PRIVATE_VAPID_KEY
environment variables.
const webpush = require('web-push');
const publicVapidKey = process.env.PUBLIC_VAPID_KEY;
const privateVapidKey = process.env.PRIVATE_VAPID_KEY;
// Replace with your email
webpush.setVapidDetails('mailto:val@karpov.io', publicVapidKey, privateVapidKey);
Next, add a /subscribe
endpoint to your Express app. Your browser JavaScript will send an HTTP request to this
endpoint with a PushSubscription
object in the request body. You need the PushSubscription
object in order to send a push
notification via webpush.sendNotification()
.
const app = express();
app.use(require('body-parser').json());
app.post('/subscribe', (req, res) => {
const subscription = req.body;
res.status(201).json({});
const payload = JSON.stringify({ title: 'test' });
console.log(subscription);
webpush.sendNotification(subscription, payload).catch(error => {
console.error(error.stack);
});
});
That's all you need on the server side. You can find the complete source on GitHub. Now,
you need to create a client client.js
and a service worker worker.js
.
Client and Service Worker
First, in order to serve up your static assets to the client, use the
express-static
npm module to configure your Express app to serve static files from the top-level directory.
Just make sure you put this app.use()
call after your /subscribe
route
handler, otherwise Express will look for a subscribe.html
file instead of
using your route handler.
app.use(require('express-static')('./'));
Next, create an index.html
file that will serve as an entry point for your
application. The only part of this file that really matters is the <script>
tag
that pulls in the client-side JavaScript, the rest is a placeholder.
<html>
<head>
<title>Push Demo</title>
<script type="application/javascript" src="/client.js"></script>
</head>
<body>
Service Worker Demo
</body>
</html>
Now that you have an entry point, create a JavaScript file called client.js
.
This file will be responsible for telling the browser to initialize
your service worker and making the HTTP request to /subscribe
. The below
example uses async/await, because if your browser supports service workers it should support async/await as well.
// Hard-coded, replace with your public key
const publicVapidKey = 'BOynOrGhgkj8Bfk4hsFENAQYbnqqLSigUUkCNaBsAmNuH6U9EWywR1JIdxBVQOPDbIuTaj0tVAQbczNLkC5zftw';
if ('serviceWorker' in navigator) {
console.log('Registering service worker');
run().catch(error => console.error(error));
}
async function run() {
console.log('Registering service worker');
const registration = await navigator.serviceWorker.
register('/worker.js', {scope: '/'});
console.log('Registered service worker');
console.log('Registering push');
const subscription = await registration.pushManager.
subscribe({
userVisibleOnly: true,
// The `urlBase64ToUint8Array()` function is the same as in
// https://www.npmjs.com/package/web-push#using-vapid-key-for-applicationserverkey
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
});
console.log('Registered push');
console.log('Sending push');
await fetch('/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'content-type': 'application/json'
}
});
console.log('Sent push');
}
Finally, you need to implement the worker.js
file that client.js
loads.
This is where your service worker logic lives. In a service worker, you get
a 'push' event when your subscription receives
a push notification.
console.log('Loaded service worker!');
self.addEventListener('push', ev => {
const data = ev.data.json();
console.log('Got push', data);
self.registration.showNotification(data.title, {
body: 'Hello, World!',
icon: 'http://mongoosejs.com/docs/images/mongoose5_62x30_transparent.png'
});
});
And that's it! Start your server with the correct environment variables:
$ env PUBLIC_VAPID_KEY='OMITTED' env PRIVATE_VAPID_KEY='OMITTED' node .
Navigate to http://localhost:3000
in Chrome, and you should see the below
push notification!
These notifications aren't just limited to Chrome, this same code works with Firefox as well.
Moving On
Web push is just one of numerous advantages service workers provide. With a single npm module, you can send push notifications to most modern browsers. Give service workers a shot next time you want to add push notifications to your web app!