How to Implement Push Notifications in Web Application Development using Firebase

Push notifications have become crucial for online platforms to interact with users and provide updates as they happen. They enable applications to send brief messages or warnings even when the user is not currently exploring the website.
Firebase Cloud Messaging (FCM) is instrumental for a web application development agency to implement push notifications. 
Keep reading this blog to explore the details.
## Client-side implementation with Firebase Cloud Messaging (FCM) in web application development
Below is a breakdown of the process of how web application development companies implement Firebase push notifications:
### Service worker registration and push events
Service workers essentially help receive push notifications in the background of a closed browser window. These are intermediaries between the web application and the push service. Here’s how a service worker registration works:
**Registration script:** The service worker logic is typically present in a JavaScript file within the web application. This script registers the service workers during the initial page load.
**Browser support check:** JavaScript verifies whether the browser can support service workers before registering them. Thus, it ensures compatibility and avoids potential errors.
navigator.serviceWorker.register: If supported, the script utilizes the navigator.serviceWorker.register() method to register the service worker. This method takes the path to the service worker script as an argument.
**Service worker lifecycle:** After successful registration, the service worker enters a lifecycle that includes activation, installation, and potentially controlling the active webpage.
**Push event reception:** Once a service worker is successfully registered, it can listen for specific events. This includes push events triggered by the push service (like Firebase Cloud Messaging) when a push notification is sent to the user's device.
```
Below is a JavaScript code sample of service worker registration in web application development:
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(registration => {
      console.log('Service worker registration successful:', registration);
    })
    .catch(error => {
      console.error('Service worker registration failed:', error);
    });
} else {
  console.log('Service workers are not supported.');
}
```
**Explanation:**
- This code snippet checks for service worker support using 'serviceWorker' in navigator.
- If supported, it registers the service worker located at /service-worker.js using navigator.serviceWorker.register().
- The .then() and .catch() methods handle successful registration and potential errors, respectively.
### Requesting user permission with notification API
The Notification API allows web applications to request permission from users to display notifications. Here's the process followed in web application development:
**API Support Check:** Similar to service worker registration, the script verifies if the browser supports the Notification API.
**Notification.requestPermission:** If supported, the script utilizes the Notification.requestPermission() method to request permission from the user. IT returns a Promise that resolves with a string indicating the user's permission choice ("granted" or "denied").

Check out a code sample of notification permission request:
```
if (Notification.permission !== 'granted') {
  Notification.requestPermission().then(permission => {
    if (permission !== 'granted') {
      console.log('Notification permission denied.');
    } else {
      console.log('Notification permission granted.');
      // (Optional) Perform actions after permission is granted
    }
  });
} else {
  console.log('Notification permission already granted.');
  // Perform actions if permission was previously granted
}
```
**Explanation:**
- This code snippet first checks the current permission status using Notification.permission.
- If permission hasn't been granted yet ('granted'), it requests permission with Notification.requestPermission().
- The .then() method handles the Promise resolution by checking the user's choice.
- Users can optionally perform actions based on the granted permission (e.g., subscribe to push service).
### Fetching registration token with Firebase SDK
Firebase provides an SDK for web applications to interact with various Firebase services, including Cloud Messaging. Here's how to fetch the registration token in a web application development process:
**Firebase Initialization:** The script initializes the Firebase app using the Firebase project configuration.

messaging.getToken: Once initialized, the script utilizes the messaging.getToken() method from the Firebase Messaging library to retrieve the registration token for the current device. This token is used to identify the device for push notifications.
Here’s a code snippet in JavaScript:
```
// Assuming Firebase app is already initialized

messaging.getToken({ vapidKey: 'YOUR_VAPID_KEY' })
  .then(currentToken => {
    if (currentToken) {
      console.log('FCM registration token:', currentToken);
      // Send the token to your server for further processing
    } else {
      console.log('No registration token available. Request permission.');
      // (Optional) Request permission if no token is available
    }
  })
  .catch(error => {
    console.error('An error occurred while retrieving token:', error);
  });
```


**Explanation:**
- This code snippet assumes the Firebase app is already initialized with the project configuration.
- It retrieves the registration token using messaging.getToken(). The vapidKey argument is essential for security purposes.
- The .then() method handles successful retrieval, allowing developers to send the token to the backend server for further processing (subscription to push service, etc.).
- The .catch() method handles potential errors during token retrieval.

## Backend implementation of push notifications in web application
While the client side handles user permissions and retrieves the registration token, the backend stores and utilizes this token to send push notifications. This section assumes a backend built with Node.js and utilizes MongoDB for database storage. Additionally, it incorporates JSON Web Tokens (JWT) for enhanced security in web application development.
### Sending the registration token (client-side)
The registration token and relevant user data are still sent to the backend using an HTTP request but with added security measures. Below is a code Sample of sending registration token with JWT in JavaScript (client-side):
```
// Assuming registration token and user ID are retrieved

const jwtPayload = {
  token: currentToken,
  userId: 123,
  // Additional user data (optional)
  exp: Math.floor(Date.now() / 1000) + (60 * 60) // Expires in 1 hour
};

const jwtSecret = 'YOUR_JWT_SECRET'; // Replace with a strong secret key

const token = jwt.sign(jwtPayload, jwtSecret);

fetch('/api/subscribe', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({
    userId: 123 // (Optional) Redundant if included in JWT
  })
})
.then(response => {
  // ... (handle response as before)
})
.catch(error => {
  // ... (handle error)
});
```
**Explanation:**
- This code snippet utilizes the jwt library (replaced with the specific library used in the project) to create a JWT token.
- The token payload includes the registration token (currentToken), user ID (userId), and an expiration time (exp).
- A strong secret key (jwtSecret) is used to signing the JWT token. Replace 'YOUR_JWT_SECRET' with an actual secret key stored securely in the environment variables.
- The request includes the JWT token in the Authorization header with the Bearer scheme.
- The request body might contain additional user data (optional), but the core information is embedded within the JWT token for security.
### Backend storage (Node.js with MongoDB)
The backend server utilizes Node.js to process the request and store the data in a MongoDB database. Here’s a code Sample (Backend - Node.js/MongoDB):
```
const express = require('express');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken'); // Same library as client-side

const app = express();

// Replace with your MongoDB connection string and database name
const mongoUri = 'mongodb://localhost:27017/your_database_name';
mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected'))
  .catch(error => console.error('MongoDB connection error:', error));

const PushSubscriptionSchema = new mongoose.Schema({
  userId: Number,
  token: String,
  // Additional user data (optional)
  exp: Number // Optional, if not included in JWT
});

const PushSubscription = mongoose.model('PushSubscription', PushSubscriptionSchema);

app.post('/api/subscribe', async (req, res) => {
  try {
    const decoded = jwt.verify(req.headers.authorization.split(' ')[1], 'YOUR_JWT_SECRET'); // Verify JWT

    const existingSubscription = await PushSubscription.findOne({ userId: decoded.userId });
    if (existingSubscription) {
      existingSubscription.token = decoded.token;
      // Update expiration if needed
      await existingSubscription.save();
      return res.json({ message: 'Subscription updated successfully.' });
    }

    const newSubscription = new PushSubscription({
      userId: decoded.userId,
      token: decoded.token,
      // Additional user data (optional)
      exp: decoded.exp // Optional, if not included in JWT
    });

    await newSubscription.save();
    res.json({ message: 'Subscription created successfully.' });
  } catch (error) {
    console.error('Error processing subscription:', error);
    res.status(500).json({ message: 'An error occurred.' });
  }
});

// ... (other application routes)

app.listen(3000, () => console.log('Server listening on port 3000'));
```
**Explanation:**
- This code snippet showcases a basic Node.js Express server with a route (/api/subscribe) to handle the incoming POST request.
- The server utilizes Mongoose to connect to a MongoDB (database and defines a schema for storing push subscription data.
- The route first verifies the JWT token sent in the Authorization header using the same secret key used for signing on the client side.
- It checks if a subscription for the provided userId already exists in the database.
- The response acknowledges successful subscription creation or update.
- Error handling is implemented to catch potential issues during JWT verification or database interactions.

## Triggering push notifications and displaying user interactions

Here’s how developers can trigger and display push notifications in web application development:
### Triggering push notifications
Backend events, like a new chat message, can trigger push notifications to inform users. Here's the process:
**Event handling:** The backend server listens to specific events (e.g., new messages received).
**Data preparation:** Upon an event, relevant data, such as the sender's name and message content, is extracted to personalize the notification.
**Target selection:** Based on the event and user data, the server determines which users to notify (e.g., all participants in a chat room).
**Push request construction:** The server constructs a push request message using the retrieved registration tokens and notification content.
Firebase Admin SDK: The server utilizes the Firebase Admin SDK (language-specific library for Node.js, Python, etc.) to send the push request to Firebase Cloud Messaging (FCM).
Below is a code Sample in JavaScript (Backend Endpoint - Node.js):
const functions = require('firebase-functions');
const admin = require('firebase-admin');
```
// ... (Firebase Admin SDK initialization)

exports.sendMessageNotification = functions.firestore
  .document('chats/{chatId}')
  .onWrite(async (change, context) => {
    const newMessage = change.after.data();
    const senderId = newMessage.senderId;
    const messageContent = newMessage.content;

    const recipientSubscriptions = await getRecipientSubscriptions(chatId); // Replace with logic to fetch subscriptions

    const notificationPromises = recipientSubscriptions.map(async (subscription) => {
      const payload = {
        notification: {
          title: `${senderId} sent a message`,
          body: messageContent,
          click_action: `openChat/${chatId}` // Optional: Action on notification click
        },
        token: subscription.token
      };

      return admin.messaging().send(payload);
    });

    await Promise.all(notificationPromises);
  });
```
**Explanation:**
- This code demonstrates a Cloud Function triggered by a write event in a Firestore chat document. It does the following:
- Extracts the sender ID and message content.
- Retrieves recipient subscriptions for the chat (replace with the logic).
- Constructs a notification payload with title, body, and an optional action for user clicks.
- Utilizes admin.messaging().send from the Firebase Admin SDK to send push requests for each recipient's token.

### Sending push requests (Firebase Admin SDK)
The Firebase Admin SDK provides libraries for various languages (Node.js, Python, etc.) in web application development. While the specific syntax might differ, the core steps for sending push requests are similar:
**Initialize SDK:** Authenticate and initialize the Firebase Admin SDK with the project credentials.
**Construct payload:** Build the notification payload with title, body, optional data, and target tokens.
**Send request:** Use the send method of the Messaging module to send the push request to FCM.
### Displaying push notifications
The service worker in the client-side application plays a crucial role in displaying push notifications. Here's how it works:
**onMessage Event:** The service worker listens for the onMessage event triggered by FCM when a push notification is received.
**showNotification Method:** Upon receiving the event, the service worker utilizes the showNotification method from the Notification API. This helps display a notification to the user.
Here’s a code Sample (displaying push notification):
```
self.addEventListener('push', function(event) {
  const data = event.data.json();

  const notificationTitle = data.notification.title;
  const notificationBody = data.notification.body;
  const notificationClickAction = data.notification.click_action; // Optional

  const options = {
    body: notificationBody,
    // ... (other notification options)
  };

  event.waitUntil(self.registration.showNotification(notificationTitle, options));

  if (notificationClickAction) {
    self.addEventListener('notificationclick', (clickEvent) => {
      if (clickEvent.notification.tag === notificationTitle) {
        // Open the relevant URL or perform an action based on click_action
      }
    });
  }
});
```
**Explanation:**
- This code snippet demonstrates a service worker event listener for push notifications. It does the following:
- Extracts notification title, body, and an optional click action from the received data.
- Utilizes self.registration.showNotification to display the notification with title and options.
- Optionally listens for the notificationclick event to handle user interaction (e.g., opening a URL) based on the click_action value included in the push payload.
## Conclusion
This guide provided a step-by-step breakdown of the client-side and backend implementation along with security considerations. By following these steps, any top custom web application development company can successfully integrate web app push notifications with Firebase. This not only enhances user engagement through timely notifications but also ensures a seamless and secure user experience.

**Web Application Development:** https://www.unifiedinfotech.net/services/web-app-development/