How to Set Up Push Notifications in Your Web App Without a Library

Posted by

Learn how to use the browser’s Push API and Service Workers to send real-time notifications, no Firebase, no third-party tools required.

Learn how to use the browser’s Push API and Service Workers to send real-time notifications, no Firebase, no third-party tools required.

Introduction

You’ve probably seen it:

“🔔 This site wants to send you notifications.”

Click Allow, and suddenly you get real-time updates even when the site’s closed.

That’s not magic.
That’s the Push API working with Service Workers.

Most developers rely on Firebase or OneSignal to do this,
but the truth is, you can set up push notifications yourself using just browser APIs and your own backend.

In this guide, you’ll learn exactly how to set up push notifications from scratch no libraries, no SDKs, just you and the browser.


1️. What You’ll Need

Before we start, make sure you have:

✅ A web server running HTTPS (Push API requires SSL)
✅ Basic JavaScript knowledge
✅ Node.js (for backend demo)


2️. How Web Push Works (The Flow)

Let’s visualize it first 👇

[ Browser ] ← subscribes → [ Push Service (e.g. Chrome Push Server) ]  
↑ ↓
Service Worker ← receives → Push Message ← [ Your Server ]

Here’s the 3-step breakdown:

  1. The browser subscribes to push notifications via a Service Worker.
  2. Your server stores that subscription info (a unique endpoint + keys).
  3. Server sends a message to the browser’s push service → browser displays it.

Everything depends on one thing: the Service Worker.


3️. Step 1: Register a Service Worker

In your main JS file:

if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js").then(() => {
console.log("✅ Service Worker registered");
});
}

This Service Worker will listen for push events later.


4️. Step 2: Ask User Permission

Before you can send notifications, you must ask permission:

async function askPermission() {
const permission = await Notification.requestPermission();
if (permission === "granted") {
console.log("✅ Notification permission granted");
} else {
console.warn("❌ Permission denied");
}
}

askPermission();

✅ If granted → you can subscribe the user.
 ⚠️ If denied → you can’t send push notifications at all.


5️. Step 3: Subscribe to Push Notifications

Once permission is granted, subscribe the user through the PushManager API:

async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;

const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array("<YOUR_VAPID_PUBLIC_KEY>"),
});

console.log("Subscription object:", JSON.stringify(subscription));
// Send this subscription object to your backend to store
}

This returns a subscription object containing:

  • The endpoint (browser-specific URL)
  • Encryption keys (for secure messages)

Helper Function: Convert Base64 Key

function urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/");
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}

6️. Step 4: Set Up a Basic Backend (Node.js Example)

You’ll need a backend to send push messages.
Let’s use Node.js + the official web-push module (no Firebase here).

Install:

npm install web-push express body-parser

Generate VAPID Keys (One-Time Setup):

npx web-push generate-vapid-keys

You’ll get:

Public Key:  BExxxxx...
Private Key: dVxxxx...

Use the public key in your frontend and the private key in your backend.


Backend Code (server.js):

import express from "express";
import bodyParser from "body-parser";
import webpush from "web-push";

const app = express();
app.use(bodyParser.json());

// Use your own keys here
const publicVapidKey = "YOUR_PUBLIC_KEY";
const privateVapidKey = "YOUR_PRIVATE_KEY";

webpush.setVapidDetails(
"you@example.com",
publicVapidKey,
privateVapidKey
);

let subscriptions = [];

app.post("/subscribe", (req, res) => {
const subscription = req.body;
subscriptions.push(subscription);
res.status(201).json({});
});

// Send push to all subscribers
app.post("/send", async (req, res) => {
const payload = JSON.stringify({ title: "Hello from Web Push!" });

const results = await Promise.allSettled(
subscriptions.map((sub) => webpush.sendNotification(sub, payload))
);

res.json({ results });
});

app.listen(4000, () => console.log("Server running on port 4000"));

✅ Stores user subscriptions in memory (use DB in production)
✅ Sends a push message with /send endpoint


7️. Step 5: Handle Push Events in the Service Worker

In your sw.js file:

self.addEventListener("push", (event) => {
const data = event.data.json();
console.log("Push received:", data);

self.registration.showNotification(data.title, {
body: "This notification was sent without any library!",
icon: "/icon.png",
});
});

Now, when you hit the /send endpoint from your server, your browser will show a real push notification even if the page is closed!


8️. Step 6: Optional: Handle Notification Clicks

Add this to your Service Worker to make notifications interactive:

self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(clients.openWindow("https://your-app-url.com"));
});

✅ Clicking the notification opens your web app.


9️. The Complete Flow Recap

✅ All done, no libraries, no SDKs.


10. Debugging Tips

⚠️ Must use HTTPS Push API, won’t work on http://.
✅ Test in Chrome/Edge (great support)
✅ Check DevTools → Application → Service Workers → Push messages
✅ Revoke and re-allow permission from browser settings if needed


Why This Matters

By setting up push notifications manually:

  • You learn how they truly work under the hood
  • You’re not locked into third-party services
  • You can customize everything: payloads, encryption, icons, and timing

It’s one of those “low effort, high impact” features that make your app feel truly native.


Conclusion

Setting up web push notifications from scratch might sound complicated, but once you understand the flow, it’s just a few moving parts: a Service Worker, a subscription, and a server that sends payloads.

You built:

  • A working push subscription system
  • A Service Worker that shows notifications
  • A backend that sends real-time messages

No Firebase. No OneSignal. No dependencies.
Just pure web APIs doing what they were designed for.

The web is powerful enough you just have to use what’s already built in.


Call to Action

Have you ever implemented push notifications without Firebase?
Drop your setup or your biggest challenge in the comments 👇

And if your teammate still thinks it’s “impossible without libraries”…
Please send them this post and let the Service Worker do the talking

Leave a Reply

Your email address will not be published. Required fields are marked *