2HAASStart a project
All posts

Guides

Guide to Implementing Web Push Notifications

Learn how to set up and send web push notifications to re-engage users. This guide covers client-side setup and server-side logic.

The 2HAAS team5 min read

Modern web applications constantly seek ways to keep users engaged and informed. A common challenge developers face is how to communicate with users effectively after they leave the website. Without a direct channel, users might forget to return, leading to decreased engagement and retention. This guide walks you through implementing web push notifications, a powerful tool for direct user communication.

Understanding Web Push Notifications

Web push notifications are messages that your website can send to users. These messages appear on their desktop or mobile device, similar to native application notifications. The unique aspect is that they function even when the user is not actively browsing your website. This capability relies on two key web technologies: Service Workers and the Push API.

Service Workers are JavaScript files that run in the background, separate from the main browser thread. They can intercept network requests, cache resources, and receive push messages from a server. The Push API, on the other hand, allows a web application to subscribe a user to push services and receive messages from a backend server.

Setting Up the Client-Side

The client-side setup involves registering a service worker and requesting user permission.

  1. Register Your Service Worker: First, create a JavaScript file, for example, service-worker.js, in your project's root directory. Then, register it from your main application script.

    // In your main application JavaScript file (e.g., app.js)
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js')
          .then(registration => {
            console.log('Service Worker registered with scope:', registration.scope);
          })
          .catch(error => {
            console.error('Service Worker registration failed:', error);
          });
      });
    }
    

    Inside service-worker.js, you will handle push events:

    // service-worker.js
    self.addEventListener('push', event => {
      const data = event.data.json();
      console.log('Push received:', data);
    
      const title = data.title || '2HAAS Notification';
      const options = {
        body: data.body || 'You have a new message!',
        icon: '/images/icon-192x192.png', // Ensure you have an icon
        badge: '/images/badge-72x72.png' // For Android, optional
      };
    
      event.waitUntil(self.registration.showNotification(title, options));
    });
    
    self.addEventListener('notificationclick', event => {
      console.log('Notification clicked!');
      event.notification.close();
      // You can open a new window or focus an existing one here
      event.waitUntil(
        clients.openWindow('https://2haas.com')
      );
    });
    
  2. Request User Permission and Subscribe: After the service worker is registered, you need to ask the user for permission to send notifications. If permission is granted, you can subscribe the user to a push service. This process generates a PushSubscription object.

    // In your main application JavaScript file (e.g., app.js)
    async function subscribeUser() {
      if (!('PushManager' in window)) {
        console.warn('Push notifications are not supported by this browser.');
        return;
      }
    
      const permissionResult = await Notification.requestPermission();
      if (permissionResult !== 'granted') {
        console.warn('Permission for notifications was not granted.');
        return;
      }
    
      const serviceWorkerRegistration = await navigator.serviceWorker.ready;
      const applicationServerKey = urlBase64ToUint8Array('YOUR_VAPID_PUBLIC_KEY'); // Replace with your key
    
      const subscription = await serviceWorkerRegistration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: applicationServerKey
      });
    
      console.log('User is subscribed:', subscription);
      // Send the subscription object to your backend server
      await fetch('/subscribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(subscription)
      });
      console.log('Subscription sent to server.');
    }
    
    // Utility function to convert VAPID key
    function 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;
    }
    
    // Call subscribeUser() when appropriate, e.g., on a button click
    // document.getElementById('subscribe-button').addEventListener('click', subscribeUser);
    

Server-Side Implementation

On the server, you need to store user subscriptions and send push notifications using a push library.

  1. Generate VAPID Keys: VAPID (Voluntary Application Server Identification) keys authenticate your server to the push service. You can generate a pair of public and private keys using a library or an online tool. Keep the private key secure and use the public key in your client-side code.

    # Example using web-push-cli in Node.js environment
    npx web-push generate-vapid-keys
    # This will output:
    # Public Key: B...
    # Private Key: ...
    
  2. Store Subscriptions: When a user subscribes, your client-side code sends the PushSubscription object to your backend. Store this object, typically in a database, associated with the user. Each subscription contains an endpoint URL and keys (p256dh and auth).

    // Example PushSubscription object
    {
      "endpoint": "https://fcm.googleapis.com/fcm/send/...",
      "expirationTime": null,
      "keys": {
        "p256dh": "...",
        "auth": "..."
      }
    }
    
  3. Send Push Notifications: To send a notification, your server uses a web push library (e.g., web-push for Node.js, pywebpush for Python). This library encrypts the payload and sends it to the push service endpoint specified in the user's subscription.

    // Example Node.js server-side code using the 'web-push' library
    const webpush = require('web-push');
    // Replace with your generated VAPID keys and contact email
    webpush.setVapidDetails(
      'mailto:[email protected]',
      'YOUR_VAPID_PUBLIC_KEY',
      'YOUR_VAPID_PRIVATE_KEY'
    );
    
    // Assuming you have an array of stored subscriptions
    // const subscriptions = getSubscriptionsFromDatabase();
    
    app.post('/send-notification', async (req, res) => {
      const payload = JSON.stringify({
        title: 'New Article from 2HAAS!',
        body: 'Check out our latest guide on Web Push Notifications.',
        url: 'https://2haas.com/blog/implement-web-push-notifications' // Optional URL for notification click
      });
    
      // Iterate through stored subscriptions and send
      for (const subscription of subscriptions) {
        try {
          await webpush.sendNotification(subscription, payload);
          console.log('Notification sent to:', subscription.endpoint);
        } catch (error) {
          console.error('Failed to send notification:', error);
          // Handle expired or invalid subscriptions (e.g., remove from database)
        }
      }
      res.status(200).send('Notifications sent successfully.');
    });
    

Best Practices and Considerations

  • User Experience: Always ask for permission at an opportune moment, not immediately on page load. Explain the value proposition.
  • Payload Size: Keep notification payloads small. Many push services have size limits (e.g., 4KB).
  • Expiration: Push services might hold notifications for a limited time if the device is offline. You can specify a ttl (Time To Live) in your push request.
  • Unsubscribing: Provide a clear way for users to unsubscribe. Remove their subscription from your database when they do.
  • Error Handling: Implement robust error handling on the server, especially for 410 Gone status codes, which indicate an expired or invalid subscription that should be removed.

Your Notification Checklist

Implementing web push notifications can significantly boost user engagement. Follow these steps:

  1. Ensure your application is served over HTTPS.
  2. Create and register your service-worker.js.
  3. Generate VAPID keys for server authentication.
  4. Implement client-side permission requests and subscription logic.
  5. Develop server-side code to store subscriptions and send notifications.
  6. Test thoroughly across different browsers and devices.
  7. Provide a clear unsubscribe mechanism for users.

Keep reading

  1. Guides4 min read

    Mastering Responsive Layouts with CSS Container Queries

    Learn how to implement CSS Container Queries for truly responsive, component-based layouts. This guide covers setup, syntax, and practical examples.

  2. Tech Tips4 min read

    Master CSS Custom Properties for Dynamic Styles

    Learn how CSS custom properties (variables) simplify your stylesheet management, enable dynamic theming, and improve maintainability for any web project.

  3. Code Snippets4 min read

    Simplify Promise Handling with Promise.withResolvers

    Learn how the new Promise.withResolvers static method streamlines common asynchronous patterns in JavaScript, reducing boilerplate and improving code clarity.

Tell us what’s in the way.

A few lines is enough. You get a candid yes, a candid no, or a sharper question.

  • Working software in week four
  • A reply in one business day
  • Handover is the deliverable

Prefer email? [email protected]

The problem, who it is for, and what is in the way right now.

One business day to a real reply. No sales sequence, no call required.