Web Push Notifications
Web push notification written in raw JavaScript without any libraries.

Daniel Gustaw
• 2 min read

Create Vite Project
pnpm create vite .
Install web-push
package.
pnpm add web-push
Generate VAPID keys
pnpm web-push generate-vapid-keys --json > keys.json
It can be done also programmatically by:
import fs from 'fs'
import push from 'web-push'
const keys = push.generateVAPIDKeys();
fs.writeFileSync('keys.json', JSON.stringify(keys, null, 2));
Get agreement on notifications
In most of the instructions you will see Notification.requestPermission()
in this place.
But it can be simplified. We can use pushManager.subscribe()
method described in:
- https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
- https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/pushManager
To have access to pushManager
we have to register service worker.
Create service worker
// public/sw.js
self.addEventListener('push', (message) => {
const payload = message.data.json();
console.log(payload);
self.registration
.showNotification(payload.title, {
body: payload.body,
})
.catch(console.error);
});
Now you can register service worker in your main script.
// src/main.ts
document.addEventListener('DOMContentLoaded', async () => {
await navigator.serviceWorker.register('sw.js');
})
Subscribe to push notifications
We can’t subscribe on notifications after page load because user have to trigger it manually.
Lets create button in main.ts
// src/main.ts
import {setupSubscription} from './subscription.ts'
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<div class="card">
<p>Agree on notifications</p>
<button id="subscribe" type="button">Subscribe</button>
</div>
</div>
`
setupSubscription(document.querySelector<HTMLButtonElement>('#subscribe')!)
and handle click in subscription.ts
// src/subscription.ts
export function setupSubscription(subscribeButton: HTMLButtonElement) {
console.log('subscribeButton', subscribeButton);
subscribeButton.addEventListener('click', async () => {
try {
const sw = await navigator.serviceWorker.ready;
const push = await sw.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: '<public vapid key>'
});
console.log(push.toJSON());
// TODO: Send subscription to server
} catch (err) {
console.error(err);
}
})
}
push
object printed in console have to be copied to back-end in next step. Lets do it manually for now.
Send notifications
Now we are going to write back-end in Node.js.
import push from 'web-push'
import keys from './keys.json' assert { type: 'json' }
push.setVapidDetails('https://myapp.com', keys.publicKey, keys.privateKey);
// there should be object copied from browser console
let sub: push.PushSubscription = {
"endpoint": "https://jmt17.google.com/fcm/send/eeg8M0Ydr0Y:APA91bE5xr9wV2hLFyMuavOJFCQqqiTybLI30fWd8wOdAMvoITBfSgs-WW4LpUWw7kn7kTb39_ornJgNPb4gCcdh-AW9HEiY2qAP7eSiwpp0dmY__-ef4fcS3RUrAbLbI2hYgphaOjNz",
"expirationTime": null,
"keys": {
"p256dh": "BORNkcqyS0qf43f4Ph058C9pBB0tiLv9JTqjYWAVfLGs472aSlsPt0lNRMdioUU3HOUg4f2lHnog34FNV0Fi_1k",
"auth": "jJwCvDwpVTThRQd5beYWzg"
}
};
const payload = JSON.stringify({
title: "Hello World",
body: "This is your second push notification"
});
push
.sendNotification(sub, payload)
.catch(console.error);
Other articles
You can find interesting also.

Selected syntax in JavaScript ES2020, ES2021 and ES2022
Nullish coalescing, Optional chaining, Proxies, Private fields, allSettled, BigInt, Dynamic Import, replaceAll, Numeric Separators, matchAll, Logical Assignment, Top level await

Daniel Gustaw
• 19 min read

The impact of indexing on search performance in MySQL database
Using indexes speeds up searches and increases table size while slowing down modifications. The article shows how to profile queries and measure the impact of indexes on search performance.

Daniel Gustaw
• 15 min read

Overload Signatures in Typescript
In TypeScript, we can specify a function that can be called in different ways by writing overload signatures. You can use this to define functions with returned type dependent from arguments values.

Daniel Gustaw
• 2 min read