Integration of notification system in your NextJS/MERN application
What is Notification System?
A notification system is a feature in modern web applications that delivers real-time updates, alerts, or messages to users based on specific events.
Providing this kind of immediate response improves the overall user experience and builds trust in the application. It also keeps users engaged by making interactions feel more responsive and meaningful, which is important for user retention and overall business growth.
Types of Notifications:
In-app notifications: In-app notifications are alerts or messages that appear directly inside a web or mobile application while the user is actively using it. They are designed to keep users informed about important updates or actions happening within the app without needing to leave the current page or refresh the interface.
These notifications are typically shown in places like notification dropdowns, pop-up banners, toast messages, or badge counters on icons. For example, a social media app might show a notification when someone likes a post or sends a friend request.
In-app notifications are usually powered by real-time communication between the client and server, often using technologies that allow instant data updates. Their main purpose is to improve user engagement and provide a smooth, interactive experience within the application itself.
This notification approach uses Socket.io for real-time handling, where users are connected to specific rooms based on their roles or context. For example, all authenticated users can be placed in an authenticatedUser room to receive general software notifications, while specific users can join separate rooms like authorityRoom for privileged or sensitive updates, such as restricted chats or administrative alerts.
Email notifications: Email notifications are messages sent to a user’s email address to inform them about important events or updates related to an application. They are used when the user does not need to be actively using the app to receive the information.
These notifications are commonly used for actions such as account verification, password resets, order confirmations, activity summaries, or important alerts. Since emails are stored in the user’s inbox, they also serve as a reliable way to keep a record of communication.
Email notifications are typically triggered from the backend when a specific event occurs in the system. The server then sends an email using an email service or SMTP provider, ensuring that the message reaches the user even if they are offline.
This notification system utilizes thirdy party email provider packages such as NodeMailer that would be assigned with a admin gmail whcih could be email of the software and through that email the notifcation would be sent to users who are logined or were member of the app.
Push notifications: Push notifications are messages that are sent directly to a user’s device or browser, even when they are not actively using the application. They appear as system-level alerts, making them highly visible and effective for grabbing user attention.
These notifications are commonly used for real-time updates such as new messages, reminders, social interactions, or important announcements. Unlike in-app notifications, push notifications can reach users even when the app is closed, as long as the user has granted permission.
Push notifications are typically delivered through a push service that handles the communication between the server and the user’s device. Once triggered, the message is instantly delivered and displayed by the browser or operating system, ensuring timely and reliable communication.

Integration of Push notification via FCM
Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that enables web applications to deliver push notifications to users, even when they are not actively interacting with the application. In this guide, we will explore how to integrate FCM into a React frontend and a Node.js backend to efficiently send and receive real-time push notifications.
Step 1: Setting Up Firebase
1.1 Creating a New Firebase Project
If you do not already have a Firebase project, follow the steps below to set one up:
Navigate to the Firebase Console at firebase
Click on “Add Project” and complete the setup process by following the on-screen instructions
Once the project is created, go to Project Settings → General
Under the “Your apps” section, click “Add app” and select Web as the platform
Register your application and copy the generated Firebase configuration object, which will be used later in your frontend integration
1.2 Obtaining Firebase Credentials for the Backend
To enable your backend to send push notifications via Firebase Cloud Messaging, you need to configure service account credentials:
In the Firebase Console, navigate to Project Settings → Service Accounts
Click on “Generate New Private Key”
A JSON file containing your service account credentials will be downloaded automatically
Store this file securely in your backend environment and ensure it is never exposed publicly or committed to version control systems such as Git.
1.3 Retrieving the VAPID Key for Web Push
Firebase Cloud Messaging (FCM) uses VAPID (Voluntary Application Server Identification for Web Push) keys to authenticate and secure web push notifications.
To generate and retrieve your VAPID key:
Navigate to Firebase Console → Project Settings → Cloud Messaging
Scroll down to the Web Push Certificates section
Click “Generate Key Pair” if a key pair has not already been created
Copy the VAPID public key, as it will be required later during frontend configuration
Step 2: Setting Up FCM in the
2.1 Installing Firebase SDK
Begin by installing the Firebase SDK in your React project:
npm install firebase
2.2 Configuring Firebase in the Frontend
Create a dedicated configuration file (e.g., firebase.js) inside your React project to initialize Firebase and configure FCM.
import { initializeApp } from "firebase/app";
import { getMessaging, getToken, onMessage, isSupported } from "firebase/messaging";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
measurementId: "YOUR_MEASUREMENT_ID"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
let messaging = null;
// Check browser support for Firebase Messaging
isSupported().then((supported) => {
if (supported) {
messaging = getMessaging(app);
console.log("Firebase Messaging is supported in this browser.");
} else{
console.warn("Firebase Messaging is not supported in this browser.");
}}
);
// Function to request notification permission and get FCM token
export const requestNotificationToken = async () => {
if (!messaging) {
console.warn("Cannot get token, Messaging is not supported.");
return null;
}
try {
const vapidKey = "YOUR_VAPID_PUBLIC_KEY";
console.log("Requesting FCM token...");
const token = await getToken(messaging, { vapidKey: vapidKey });
if (token) {
console.log("FCM Token:", token);
return token;
} else {
console.warn("No registration token available.");
return null;
}
} catch (error) {
console.error("Error retrieving FCM token:", error);
return null;
}
};
// Handle foreground notifications
if (messaging) {
onMessage(messaging, (payload) => {
console.log("Push Notification Received:", payload);
});
}
export default messaging;
2.3 Creating the Firebase Service Worker
Create a file named firebase-messaging-sw.js inside the public directory of your React project:
importScripts("https://www.gstatic.com/firebasejs/10.10.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.10.0/firebase-messaging-compat.js");
firebase.initializeApp({
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
measurementId: "YOUR_MEASUREMENT_ID"
});
const messaging = firebase.messaging();
// Handle background notifications
messaging.onBackgroundMessage((payload) => {
console.log("Received background message:", payload);
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
icon: "/your-custom-icon.png", // Custom notification icon
});
});
Step 3: Setting Up FCM in the Backend
3.1 Installing Firebase Admin SDK
In your Node.js/Express backend, install the Firebase Admin SDK, which allows your server to send push notifications to client devices:
npm install firebase-admin
3.2 Configuring Firebase in the Backend
Create a configuration file (e.g., firebase-admin.js) in your backend project to initialize the Firebase Admin SDK.
import admin from "firebase-admin";
import { ServiceAccount } from "firebase-admin";
import serviceAccount from "./firebaseServiceAccount.json";
admin.initializeApp({
credential: admin.credential.cert(serviceAccount as ServiceAccount),
});
export default admin;
3.3 Creating Routes to Send Notifications
In this step, we define backend routes to store FCM tokens and send push notifications to specific users.
- Schema of user with fcmToken
model User {
id String @id @default(cuid())
email String @unique
password String
// Store multiple FCM tokens for multi-device support
fcmTokens String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
- Login route: Saves the fcm token along with user's data
// Frontend
const login=async(email,pass)=>{
const fcmToken=await requestNotificationToken();
const res=await axios.post("/login",{
email,pass,fcmToken
});
if(res.status == 200){
console.log("Login succesful! FcmToken saved");
}
}
// Backend
async function login(req, res) {
const { userId, token } = req.body;
await prisma.user.update({
where: { id: userId },
data: {
fcmTokens: {
push: token, // save token to the table
},
},
});
res.json({ message: "FCM token saved successfully" });
}
Send Notification Route: We are using friend request concept for sending notification as a request.
// Frontend
async function sendFriendRequest(toId, fromId) {
const response = await axios.post(
"/send-friend-req",
{
toId, // id of another user
fromId, // id of your user
message: "sent you a friend request",
}
);
console.log(response.data);
}
// Backend
import admin from "../firebase-admin.js";
import prisma from "../prismaClient.js";
export async function sendNotification(req, res) {
const { message, toId, fromId } = req.body;
// Get sender (Rudy)
const sender = await prisma.user.findUnique({
where: { id: fromId },
select: { name: true },
});
// Get receiver (Jack)
const receiver = await prisma.user.findUnique({
where: { id: toId },
select: { fcmTokens: true, name: true },
});
const token = receiver.fcmTokens[0];// latest device
const notificationPayload = {
token,
notification: {
title: "New Friend Request!",
body: `${sender.name} sent you a friend request`,
icon: "https://yourwebsite.com/custom-icon.png",
},
data: {
fromId: String(fromId),
toId: String(toId),
},
};
const response = await admin.messaging().send(notificationPayload); //sending notification
console.log("Notification sent successfully:", response);
res.json({
message: "Notification sent!",
response,
});
}
Conclusion
A well-designed notification system transforms your Next.js/MERN app from passive to responsive—keeping users informed, engaged, and more likely to return. By combining in-app notifications (real-time UI updates via Socket.io or WebSockets), persistent storage (MongoDB) for history and state, and thoughtful UX (badges, toasts, preference controls, and accessibility), you deliver timely information without overwhelming users.
Key takeaways and next steps:
Use real-time channels (Socket.io/WebSockets) for instant in-app updates and fall back to push/email for background or offline delivery.
Persist notifications in your database so users can revisit history and sync across devices.
Secure and authenticate WebSocket connections; validate events server-side and apply rate limits to prevent abuse.
Design for scale (Socket.io adapters like Redis), test under load, and monitor delivery/engagement metrics.
Provide user controls for preferences and ensure notifications are accessible (screen-readers, focus management).
Start small—implement a basic flow (server emits events → client displays toasts and updates a badge → notifications saved to MongoDB), then iterate on reliability, scalability, and user controls. With incremental improvements, notifications will become a reliable channel that enhances UX and adds measurable value to your application.


