![Kate Jeffreys](https://web.dev/images/authors/katejeffreys.jpg) Kate Jeffreys ![Kayce Basques](https://web.dev/images/authors/kaycebasques.jpg) Kayce Basques [X](https://twitter.com/kaycebasques) [GitHub](https://github.com/kaycebasques)

<br />

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?](https://web.dev/articles/push-notifications-overview.md.txt#how)
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](https://web.dev/articles/push-notifications-client-codelab).

## Browser compatibility

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

- Windows: Chrome, Edge
- macOS: Chrome, Firefox
- Android: Chrome, Firefox

This codelab **does 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](https://expressjs.com/).
- The [web-push](https://www.npmjs.com/package/web-push) Node.js library handles all of the push notification logic.
- Subscription data is written to a JSON file using [lowdb](https://www.npmjs.com/package/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

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](https://web.dev/articles/push-notifications-overview.md.txt#sign)
to learn why.

1. In the terminal, run `npx web-push generate-vapid-keys`. Copy the private key and public key values.
2. 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:

   ```text
   VAPID_PUBLIC_KEY="BKiwTvD9HA..."
   VAPID_PRIVATE_KEY="4mXG9jBUaU..."
   VAPID_SUBJECT="mailto:test@test.test"
   ```
3. Open `public/index.js`.

4. 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.

Read [Subscribe the client to push notifications](https://web.dev/articles/push-notifications-overview.md.txt#subscription)
for more context about the subscription process.

### Save new subscription information

1. Click **Register service worker** in the app tab. In the status box you
   should see a message similar to this:

   ```text
   Service worker registered. Scope: https://example.com
   ```
2. In the app tab click **Subscribe to push**. Your browser or operating system
   may ask if you want to let the website send you push notifications.

3. Click **Allow** (or the equivalent phrase used by your browser or
   operating system). In the status box you should see a message like this:

   ```text
   Service worker subscribed to push.  Endpoint: https://fcm.googleapis.com/fcm/send/...
   ```

   > [!NOTE]
   > **Note:** The endpoint URL changes depending on what browser you're using. For example, on Firefox the URL starts with `https://updates.push.services.mozilla.com/`.

4. Open the terminal to see the logs. You
   should see `/add-subscription` followed by some data. `/add-subscription` is
   the URL that the client sends a
   [POST](https://developer.mozilla.org/docs/Web/HTTP/Methods/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.

5. Open `server.js`.

6. 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);
    });

> [!NOTE]
> **Note:** The database writes to `.data/db.json`. To inspect this file, open the terminal and run `cat .data/db.json`.

### Delete old subscription information

1. Go back to the app tab.
2. Click **Unsubscribe from push**.
3. Look at the 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:

       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](https://web.dev/articles/push-notifications-overview.md.txt#send),
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);
       });

2. Update the `sendNotifications()` function with the following code:

       function sendNotifications(subscriptions) {
         // 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} `);
             });
         });
       }

3. 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);
         }
       });

4. Go back to the app tab.

5. 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.

6. Open your app on other supported browsers or devices. Try subscribing to push
   notifications and then click the **Notify all** button. You should receive the
   same notification on all of your subscribed devices.

## Next steps

- Read [Push notifications overview](https://web.dev/articles/push-notifications-overview.md.txt) for a deeper conceptual understanding of how push notifications work.
- Check out [Codelab: Build a push notification client](https://web.dev/articles/push-notifications-client-codelab.md.txt) 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.