Some organizations restrict persistent connections like WebSockets, yet teams still need timely notifications even when the app isn’t open or focused.
With Web Push—the Push API, a Service Worker, and VAPID—servers can push messages reliably without keeping a socket alive, including when the page is backgrounded or closed.
Why Web Push
- Works in the background via a Service Worker and shows native notifications using the Notifications API for consistent, system‑level UX.
- Standards‑based, requires HTTPS, and uses VAPID keys so your server is identified securely to push services.
How it fits together
- App registers a Service Worker and requests notification permission from the user on a secure origin.
- App subscribes with Push Manager to get a unique subscription endpoint and keys for that browser/device.
- Server stores subscriptions and later sends payloads signed with VAPID using a lightweight library.
- The Service Worker receives the push event and displays a native notification immediately.
Client: register SW and subscribe
// Convert base64 VAPID public key to Uint8Array
function base64ToUint8Array(base64) {
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
const b64 = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/');
const raw = atob(b64);
const output = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; ++i) output[i] = raw.charCodeAt(i);
return output;
}
async function subscribeToPush(vapidPublicKeyBase64) {
const registration = await navigator.serviceWorker.register('/sw.js');
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64ToUint8Array(vapidPublicKeyBase64),
});
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
}
The app subscribes via the Push API on a secure context and sends the resulting subscription to the backend for later use.
Service Worker: receive and notify
// /sw.js
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : { title: 'Update', body: 'New alert' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon.png',
data: data.url || '/',
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const url = event.notification.data || '/';
event.waitUntil(clients.openWindow(url));
});
The Service Worker handles the push event payload and displays a native notification using the Notifications API.
Server (Node/Express): VAPID and send
// npm i express web-push
import express from 'express';
import webpush from 'web-push';
const app = express();
app.use(express.json());
// 1) Configure VAPID (generate once and set via env)
webpush.setVapidDetails(
'mailto:admin@example.com',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
);
// 2) Store subscriptions (replace with MongoDB in production)
const subscriptions = new Map();
app.get('/api/push/public-key', (_req, res) => {
res.json({ publicKey: process.env.VAPID_PUBLIC_KEY });
});
app.post('/api/push/subscribe', (req, res) => {
const sub = req.body;
subscriptions.set(sub.endpoint, sub);
res.status(201).json({ ok: true });
});
app.post('/api/push/send', async (req, res) => {
const payload = JSON.stringify({
title: 'Policy update',
body: 'Click to review changes',
url: '/inbox',
});
const results = [];
for (const sub of subscriptions.values()) {
try {
await webpush.sendNotification(sub, payload);
results.push({ ok: true });
} catch {
results.push({ ok: false });
}
}
res.json({ sent: results.length });
});
app.listen(3000, () => console.log('Server running on 3000'));
The web‑push library signs payloads with VAPID and delivers to each saved subscription endpoint, letting servers send messages without maintaining a persistent connection.
Practical tips
- Only request permission at meaningful moments to avoid prompt fatigue and improve opt‑in rates.
- Subscriptions can expire; handle send failures by pruning invalid endpoints and re‑subscribing when needed.
- Push requires HTTPS and secure contexts; keep VAPID keys safe and reuse the same key pair across deploys per environment policy
If WebSocket's are off the table, Web Push gives reliable, secure, background delivery with a small footprint—perfect for “must‑know” alerts in constrained environments.
Top comments (0)