Learn Measure Blog Live About

Codelab: Build a push notification server

This codelab shows you, step-by-step, how to build a push notification server. By the end of the codelab you'll have a server that:

  • Keeps track of push notification subscriptions (i.e. the server creates a new database record when a client opts in to push notifications, and it deletes an existing database record when a client opts out)
  • Sends a push notification to a single client
  • Sends a push notification to all subscribed clients

This codelab is focused on helping you learn by doing and doesn't talk about concepts much. Check out How do push notifications work? to learn about push notification concepts.

The client code of this codelab is already complete. You'll only be implementing the server in this codelab. To learn how to implement a push notification client, check out Codelab: Build a push notification client.

Check out push-notifications-server-codelab-complete (source) to see the complete code.

Browser compatibility

This codelab is known to work with the following operating system and browser combinations:

  • macOS: Chrome, Firefox
  • Android: Chrome, Firefox

This codelab is known to not work with the following operating systems (or operating system and browser combinations):

  • macOS: Brave, Edge, Safari
  • iOS

Application stack

  • The server is built on top of Express.js.
  • The web-push Node.js library handles all of the push notification logic.
  • Subscription data is written to a JSON file using lowdb.

You don't have to use any of these technologies to implement push notifications. We chose these technologies because they provide a reliable codelab experience.

Setup

Get an editable copy of the code

The code editor that you see to the right of these instructions will be called the Glitch UI throughout this codelab.

  1. Click Remix to Edit to make the project editable.

Set up authentication

Before you can get push notifications working, you need to set up your server and client with authentication keys. See Sign your web push protocol requests to learn why.

  1. Open the Glitch terminal by clicking Tools and then clicking Terminal.
  2. In the terminal, run npx web-push generate-vapid-keys. Copy the private key and public key values.
  3. Open .env and update VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY. Set VAPID_SUBJECT to mailto:test@test.test. All of these values should be wrapped in double quotes. After making your updates, your .env file should look similar to this:
VAPID_PUBLIC_KEY="BKiwTvD9HA…"
VAPID_PRIVATE_KEY="4mXG9jBUaU…"
VAPID_SUBJECT="mailto:test@test.test"
  1. Close the Glitch terminal.
Gotchas!

Environment variable values (the stuff in .env) are unique to a single Glitch project. I.e. if you remix your project, the values in .env won't get copied over.

  1. Open public/index.js.
  2. Replace VAPID_PUBLIC_KEY_VALUE_HERE with the value of your public key.

Manage subscriptions

Your client handles most of the subscription process. The main things your server needs to do are save new push notification subscriptions and delete old subscriptions. These subscriptions are what enable you to push messages to clients in the future. See Subscribe the client to push notifications for more context about the subscription process.

Save new subscription information

  1. To preview the site, press View App. Then press Fullscreen fullscreen.

Key Term: We'll refer to the tab that you just opened as the app tab.

  1. Click Register service worker in the app tab. In the status box you should see a message similar to this:
Service worker registered.
Scope: https://desert-cactus-sunset.glitch.me/
  1. In the app tab click Subscribe to push. In the status box you should see a message similar to this:
Service worker subscribed to push. 
Endpoint: https://fcm.googleapis.com/fcm/send/…

The endpoint URL changes depending on what browser you're using. For example, on Firefox the URL looks starts with https://updates.push.services.mozilla.com/….

  1. Go back to your code by clicking View Source in the Glitch UI.
  2. Open the Glitch Logs by clicking Tools and then clicking Logs. You should see /add-subscription followed by some data. /add-subscription is the URL that the client sends a POST request to when it wants to subscribe to push notifications. The data that follows is the client's subscription information that you need to save.
  3. Open server.js.
  4. Update the /add-subscription route handler logic with the following code:
app.post('/add-subscription', (request, response) => {
console.log('/add-subscription');
console.log(request.body);
console.log(`Subscribing ${request.body.endpoint}`);
db.get('subscriptions')
.push(request.body)
.write();
response.sendStatus(200);
});

The database writes to .data/db.json. To inspect this file in Glitch, click Tools, then click Terminal, then run cat .data/db.json in the Terminal. .data/db.json is deleted every time that you edit your app. This is because Glitch runs the start script in package.json every time you edit your app, and that script includes a call to rm .data/db.json.

Delete old subscription information

  1. Go back to the app tab.
  2. Click Unsubscribe from push.
  3. Look at the Glitch Logs again. You should see /remove-subscription followed by the client's subscription information.
  4. Update the /remove-subscription route handler logic with the following code:
app.post('/remove-subscription', (request, response) => {
console.log('/remove-subscription');
console.log(request.body);
console.log(`Unsubscribing ${request.body.endpoint}`);
db.get('subscriptions')
.remove({endpoint: request.body.endpoint})
.write();
response.sendStatus(200);
});

Send notifications

As explained in Send a push message, your server doesn't actually send the push messages directly to clients. Rather, it relies on a push service to do that. Your server basically just kicks off the process of pushing messages to clients by making web service requests (web push protocol requests) to a web service (the push service) owned by the browser vendor that your user uses.

  1. Update the /notify-me route handler logic with the following code:
app.post('/notify-me', (request, response) => {
console.log('/notify-me');
console.log(request.body);
console.log(`Notifying ${request.body.endpoint}`);
const subscription =
db.get('subscriptions').find({endpoint: request.body.endpoint}).value();
sendNotifications([subscription]);
response.sendStatus(200);
});
  1. Update the sendNotifications() function with the following code:
function sendNotifications(subscriptions) {
// TODO
// Create the notification content.
const notification = JSON.stringify({
title: "Hello, Notifications!",
options: {
body: `ID: ${Math.floor(Math.random() * 100)}`
}
});
// Customize how the push service should attempt to deliver the push message.
// And provide authentication information.
const options = {
TTL: 10000,
vapidDetails: vapidDetails
};
// Send a push message to each client specified in the subscriptions array.
subscriptions.forEach(subscription => {
const endpoint = subscription.endpoint;
const id = endpoint.substr((endpoint.length - 8), endpoint.length);
webpush.sendNotification(subscription, notification, options)
.then(result => {
console.log(`Endpoint ID: ${id}`);
console.log(`Result: ${result.statusCode}`);
})
.catch(error => {
console.log(`Endpoint ID: ${id}`);
console.log(`Error: ${error} `);
});
});
}
  1. Update the /notify-all route handler logic with the following code:
app.post('/notify-all', (request, response) => {
console.log('/notify-all');
response.sendStatus(200);
console.log('Notifying all subscribers');
const subscriptions =
db.get('subscriptions').cloneDeep().value();
if (subscriptions.length > 0) {
sendNotifications(subscriptions);
response.sendStatus(200);
} else {
response.sendStatus(409);
}
});
  1. Go back to the app tab.
  2. Click Unsubscribe from push and then click Subscribe to push again. This is only necessary because, as mentioned before, Glitch restarts the project every time you edit the code and the project is configured to delete the database on startup.
  3. Click Notify me. You should receive a push notification. The title should be Hello, Notifications! and the body should be ID: <ID> where <ID> is a random number.
  4. Open your app on other browsers or devices and try subscribing them to push notifications and then clicking the Notify all button. You should receive the same notification on all of your subscribed devices (i.e. the ID in the body of the push notification should be the same).

Next steps

  • Read Push notifications overview for a deeper conceptual understanding of how push notifications work.
  • Check out Codelab: Build a push notification client to learn how to build a client that requests notification permission, subscribes the device to receive push notifications, and uses a service worker to receive push messages and display the messages as notifications.