How to add Web Push notifications using React and Node JS
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
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 runnpm install
after that run npm run dev. It will run your React JS server.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:
express:
npm i express
. we are using this for routing and getting and sending the API response.cors:
npm i cors
. we are using this to handle Cors error.web-push:
npm i web-push
. we are using this to send push notifications.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>
);
};
Here, inside the
useEffect
I am checking if the browser supports service workers, and if it does, it calls thesend
function.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.The
send
function is an asynchronous function that performs three main tasks:It registers a service worker located at '/worker.js' with a scope of '/'.
It subscribes to push notifications using the registered service worker. The
pushManager.subscribe
method is called with an options object that specifiesuserVisibleOnly: true
and provides anapplicationServerKey
, which is generated by theurlBase64ToUint8Array
function.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.
The
urlBase64ToUint8Array
function is a utility function that converts a base64 string to a Uint8Array. This is necessary because theapplicationServerKey
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 usingwindow.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'
});
});
Now, go to the public folder create worker.js, and add this code.
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.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.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.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.
Here I have created a subscription variable to store our subscription data since we are not using a database.
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>
);
Here, I have created this function and I am calling this using the onClick event listener.
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({});
});
webpush.setVapidDetails
is a method from theweb-push
library that sets the VAPID details for your application server. It takes three arguments. I have also added my keys.Here, I have created /sendNotification endpoint.
It i am creating a payload and adding inside
webpush.sendNotification
methodThis 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.
Subscribe to my newsletter
Read articles from Sajib Hossain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by