How to add Web Push notifications using React and Node JS

Sajib HossainSajib Hossain
6 min read

Web push notifications are the easiest way to send notifications to users completely free. In this article, we will learn how to implement web push notifications using React.js and Node.js.

Frontend project setup

  1. Setup React JS using Vite js. Run this command npm create vite@latest then give your project a name and enter. then select React and enter after that select Javascript. Go to the project directory using cd and run npm install after that run npm run dev. It will run your React JS server.

  2. Now on the frontend project terminal, we have to install web-push. to install it run npm i web-push

You should see your project running on the port 5173

Backend project setup

To set up a Node JS project. First run npm init -y this will create a package.json file for you. then we have to install some packages such as:

  1. express: npm i express . we are using this for routing and getting and sending the API response.

  2. cors: npm i cors . we are using this to handle Cors error.

  3. web-push: npm i web-push . we are using this to send push notifications.

  4. nodemon: npm i nodemon . we are using this to automatically restart our server when we save our file after making any changes.

Now Let's start our backend server. but before that, we have to do some work.

// create server.js file in the root directory. :)
import express from 'express';
import cors from 'cors';
import webpush from 'web-push';

const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(3000, () => {
    console.log('Server listening on port 3000');
});
{
  "name": "blog",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module", // <======
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon server.js" // <============
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "nodemon": "^3.0.3",
    "web-push": "^3.6.7"
  }
}

Now run your project using npm start . you will see the server started on the port 3000

Setup notification subscription system

In the frontend we are not going to create a form for subscription since we don't have any database, we are going to use useEffect hook from react to subscribe to our users when they load the page. let's do that.

const publicVapidKey = 'BEeBXW50xi0b2Oc1nCeaTBmg_fQn7_K13k2c3m3KFs0d95A1JOXXXXXX';

const App = () => {
  useEffect(() => {
    if ('serviceWorker' in navigator) {
      send().catch(err => console.error(err));
    }
  }, []);

  const send = async () => {
    const register = await navigator.serviceWorker.register('/worker.js', {
      scope: '/'
    });

    const subscription = await register.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
    });

    await fetch('http://localhost:3000/subscribe', {
      method: 'POST',
      body: JSON.stringify(subscription),
      headers: {
        'content-type': 'application/json'
      }
    });
  };

  const urlBase64ToUint8Array = (base64String) => {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
      .replace(/\-/g, '+')
      .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
      outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
  };

  return (
    <div>
     hello
    </div>
  );
};
  1. Here, inside the useEffect I am checking if the browser supports service workers, and if it does, it calls the send function.

  2. We have to generate our VAPID keys for web push. npx web-push generate-vapid-keys [--json] . This will generate the public and private keys. Store them in an env.

  3. The send function is an asynchronous function that performs three main tasks:

    1. It registers a service worker located at '/worker.js' with a scope of '/'.

    2. It subscribes to push notifications using the registered service worker. The pushManager.subscribe method is called with an options object that specifies userVisibleOnly: true and provides an applicationServerKey, which is generated by the urlBase64ToUint8Array function.

    3. It sends a POST request to 'http://localhost:3000/subscribe' with the subscription object as the body. The subscription object contains all the information needed to send a push notification to the client.

    4. The urlBase64ToUint8Array function is a utility function that converts a base64 string to a Uint8Array. This is necessary because the applicationServerKey needs to be in the form of a Uint8Array. The function first pads the base64 string to ensure its length is a multiple of 4. It then replaces '-' with '+' and '_' with '/'. After that, it decodes the base64 string to a binary string using window.atob. Finally, it creates a new Uint8Array and fills it with the char codes of the binary string.

self.addEventListener('push', e => {
    const data = e.data.json();
    self.registration.showNotification(data.title, {
        body: 'Notified by Web Push',
        icon: 'http://image.ibb.co/frYOFd/tmlogo.png'
    });
});
  1. Now, go to the public folder create worker.js, and add this code.

  2. self.addEventListener('push', e => {...});: This line sets up an event listener for 'push' events. When a 'push' event occurs, the function provided as the second argument is called.

  3. const data =e.data.json();: The 'push' event (e) carries data with it. This line extracts that data and converts it from JSON format into a JavaScript object.

  4. self.registration.showNotification(data.title, {...});: This line displays a notification to the user. The title of the notification is taken from the data received with the 'push' event.

  5. The second argument to showNotification is an options object, where you can specify additional parameters for the notification. In this case, the body of the notification is set to 'Notified by Web Push', and the icon is set to the URL 'http://image.ibb.co/frYOFd/tmlogo.png'.

Now in the backend, We have to create our /subscribe API endpoint. let's do that.

  1. Here I have created a subscription variable to store our subscription data since we are not using a database.

  2. when a user makes a post request /subscribe it will store the data on this variable.

    Let's reload our frontend and see what does subscription variable prints.

    I have reloaded my frontend page and it sent a request to the /subscribe endpoint and the backend printed these details.

Send Notification

// App.jsx  
const sendNotification = async () => {
    await fetch('http://localhost:3000/sendNotification', {
      method: 'POST',
      headers: {
        'content-type': 'application/json'
      }
    });
  };

  return (
    <div>
      <button onClick={sendNotification}>Send Notification</button>
    </div>
  );
  1. Here, I have created this function and I am calling this using the onClick event listener.

  2. in the sendNotification function, I am making a post request to /sendNotification API endpoint

Let's create the endpoint in the Backend

// server.js
const vapidKeys = {
    publicVapidKey: 'BEeBXW50xi0b2Oc1nCeaTBmg_fQn7_K13k2c3m3KFs0d95A1JXXXXXXX',
    privateVapidKey: '6eaKH86AL0-LdhX_z_XXXX'
}

webpush.setVapidDetails('mailto:sajib@gmail.com', vapidKeys.publicVapidKey, vapidKeys.privateVapidKey);

app.post('/sendNotification', (req, res) => {
    const payload = JSON.stringify({ title: 'Push Test' });
    webpush.sendNotification(subscription, payload).catch(error => console.error(error));

    res.status(201).json({});
});
  1. webpush.setVapidDetails is a method from the web-push library that sets the VAPID details for your application server. It takes three arguments. I have also added my keys.

  2. Here, I have created /sendNotification endpoint.

  3. It i am creating a payload and adding inside webpush.sendNotification method

  4. This method takes two arguments: the subscription to send the notification to, and the payload of the notification.

Now, On the frontend let's click the "Send Notification" button and see if it sends a notification.

Yes, It has sent me a notification. Isn't this cool?

Thank you for reading this blog. if you like this blog then give a thumbs up. also, follow me on social media.

GitHub: https://github.com/sajibhn/push-notification

0
Subscribe to my newsletter

Read articles from Sajib Hossain directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sajib Hossain
Sajib Hossain