Open In App

Creating Notification Section in MERN Stack Social Media Platform

Last Updated : 23 Jul, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

Notifications are the fundamental feature of modern social media platforms. They serve as real-time alerts, keeping users engaged, informed, and connected to their online communities. From notifying users of new messages, likes, and comments to reminding them about events or account activities, notifications are important in shaping user interaction.

Key Features

  • Alerts that notify users of direct messages, mentions, or group chats. These keep the conversation active and prompt quick responses.
    • Example: WhatsApp push notifications for new messages or Twitter alerts when someone mentions a user in a tweet.
  • Reminders of upcoming events or milestones, such as birthdays, live streams, or public events users are interested in attending.
    • Example: Facebook reminding users about a friend’s birthday or LinkedIn notifying users about upcoming webinars.
  • Notifications that suggest content, people to follow, or groups to join based on user behaviour and preferences.
    • Example: YouTube recommends videos or Twitter suggests following accounts based on recent interactions.

Approach to Implement Social Media Platforms: Notifications

Backend

  • Notification Model: Create a schema with fields for the user, recipients, URL, content, image, and read status.
  • API Routes: Define routes for creating, retrieving, updating, and deleting notifications (e.g., /notify, /notifies, etc.).
  • Controller Logic: Implement controller functions to handle the logic for saving notifications, marking them as read, and removing them.
  • Authentication: Secure routes with middleware (like auth) to ensure only authenticated users can manage notifications.
  • Frontend: Fetch and display notifications in the UI, showing alerts or badges for unread ones.
  • Real-time Updates: Optionally use websockets (e.g., Socket.io) for real-time notification updates.
  • User Experience: Ensure notifications are concise, informative, and visually accessible to users for better engagement.

Frontend

  • UI Display: Use a notification bell or icon with a badge to show the count of unread notifications.
  • Fetch Notifications: On component mount, fetch notifications from the backend API and display them in a dropdown or list.
  • Real-time Updates: Use websockets (e.g., Socket.io) or polling to update notifications in real-time.
  • Mark as Read: Allow users to mark notifications as read by clicking on them, updating their status in the backend.
  • User Feedback: Show visual feedback (e.g., toast or alert) when new notifications arrive or are marked as read.

Backend Example

JavaScript
// index.js

require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const SocketServer = require("./socketServer");
const app = express();

app.use(express.json());
app.use(
    cors({
        origin: [, "http://localhost:5173"],
        methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
        allowedHeaders: ["Content-Type", "Authorization"],
        credentials: true,
    })
);
app.use(cookieParser());

app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    next();
});

//#region // !Socket
const http = require("http").createServer(app);
const io = require("socket.io")(http);

io.on("connection", (socket) => {
    SocketServer(socket);
});

//#endregion
app.get("/", (req, res) => {
    res.send("Hi Welcome to Social Media App API.....");
});
//#region // !Routes
app.use("/api", require("./routes/authRouter"));
app.use("/api", require("./routes/userRouter"));
app.use("/api", require("./routes/postRouter"));
app.use("/api", require("./routes/commentRouter"));
app.use("/api", require("./routes/adminRouter"));
app.use("/api", require("./routes/notifyRouter"));
app.use("/api", require("./routes/messageRouter"));
//#endregion

const URI = process.env.MONGO_UI;
mongoose.connect(
    URI,
    {
        useCreateIndex: true,
        useFindAndModify: false,
        useNewUrlParser: true,
        useUnifiedTopology: true,
    },
    (err) => {
        if (err) throw err;
        console.log("Database Connected!!");
    }
);

const port = process.env.PORT || 3001;
http.listen(port, () => {
    console.log("Listening on ", port);
});
JavaScript
// Models/notifyModel.js

const mongoose = require("mongoose");
const { Schema } = mongoose;

const notifySchema = new Schema(
    {
        id: mongoose.Types.ObjectId,
        user: { type: mongoose.Types.ObjectId, ref: "user" },
        recipients: [mongoose.Types.ObjectId],
        url: String,
        text: String,
        content: String,
        image: String,
        isRead: { type: Boolean, default: false },
    },
    {
        timestamps: true,
    }
);

module.exports = mongoose.model("notify", notifySchema);
JavaScript
// controllers/notifyCtrl.js

const Notifies = require("../models/notifyModel");

const notifyCtrl = {
    createNotify: async (req, res) => {
        try {
            const { id, recipients, url, text, content, image } = req.body;

            if (recipients.includes(req.user._id.toString())) return;

            const notify = new Notifies({
                id,
                recipients,
                url,
                text,
                content,
                image,
                user: req.user._id,
            });

            await notify.save();
            return res.json({ notify });
        } catch (err) {
            return res.status(500).json({ msg: err.message });
        }
    },

    removeNotify: async (req, res) => {
        try {
            const notify = await Notifies.findOneAndDelete({
                id: req.params.id,
                url: req.query.url,
            });
            return res.json({ notify });
        } catch (err) {
            return res.status(500).json({ msg: err.message });
        }
    },

    getNotifies: async (req, res) => {
        try {
            const notifies = await Notifies.find({ recipients: req.user._id })
                .sort("-createdAt")
                .populate("user", "avatar username");

            return res.json({ notifies });
        } catch (err) {
            return res.status(500).json({ msg: err.message });
        }
    },

    isReadNotify: async (req, res) => {
        try {
            const notifies = await Notifies.findOneAndUpdate(
                { _id: req.params.id },
                {
                    isRead: true,
                }
            );

            return res.json({ notifies });
        } catch (err) {
            return res.status(500).json({ msg: err.message });
        }
    },

    deleteAllNotifies: async (req, res) => {
        try {
            const notifies = await Notifies.deleteMany({ recipients: req.user._id });

            return res.json({ notifies });
        } catch (err) {
            return res.status(500).json({ msg: err.message });
        }
    },
};

module.exports = notifyCtrl;
JavaScript
// routes/notifyRoutes.js

const router = require('express').Router();
const auth = require('../middleware/auth');
const notifyCtrl = require('../controllers/notifyCtrl');

router.post('/notify', auth, notifyCtrl.createNotify);

router.delete('/notify/:id', auth, notifyCtrl.removeNotify);

router.get("/notifies", auth, notifyCtrl.getNotifies);

router.patch("/isReadNotify/:id", auth, notifyCtrl.isReadNotify);

router.delete("/deleteAllNotify", auth, notifyCtrl.deleteAllNotifies);

module.exports = router;


Start your backend using the below command:

node index.js


Frontend Example

JavaScript
// App.jsx

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import SearchUsers from './components/SearchUsers.jsx';
import UserProfile from './components/UserProfile.jsx';
import UpdateProfile from './components/UpdateProfile.jsx';
import Register from './components/Register';
import Login from './components/Login';
import ChangePassword from './components/ChangePassword';
import AdminRegister from './components/AdminRegister';
import Navbar from './components/Navbar';
import HomePage from './pages/HomePage';
import PostPage from './pages/PostPage';
import UserPostsPage from './pages/UserPostsPage';
import SavedPostsPage from './pages/SavedPostsPage';
import ConversationsPage from './pages/ConversionPage.jsx';
import ChatPage from './pages/ChatPage';
import CreatePost from './pages/CreatePost.jsx';

const App = () => {
    return (
        <Router>
            <Navbar />
            <Routes>
                <Route path="/" element={<HomePage />} />
                <Route path="/posts/:id" element={<PostPage />} />
                <Route path="/user-posts" element={<UserPostsPage />} />
                <Route path="/post" element={<CreatePost />} />
                <Route path="/saved-posts" element={<SavedPostsPage />} />
                <Route path="/search" element={<SearchUsers />} />
                <Route path="/user/:id" element={<UserProfile />} />
                <Route path="/update-profile" element={<UpdateProfile />} />
                <Route path="/register" element={<Register />} />
                <Route path="/login" element={<Login />} />
                <Route path="/change-password" element={<ChangePassword />} />
                <Route path="/admin-register" element={<AdminRegister />} />
                <Route path="/con" element={<ConversationsPage />} />
                <Route path="/chat/:id" element={<ChatPage />} />
            </Routes>
        </Router>
    );
};

export default App;
JavaScript
// src/pages/ConversionPage.jsx

import React, { useEffect, useState } from "react";
import { List, Typography } from "@mui/material";
import axios from "axios";
import Conversation from "../components/Conversion";

const ConversationsPage = () => {
    const [conversations, setConversations] = useState([]);

    useEffect(() => {
        axios
            .get("http://localhost/3001/api/conversations")
            .then((res) => setConversations(res.data.conversations))
            .catch((err) => console.log(err));
    }, []);

    return (
        <div>
            <Typography variant="h4" gutterBottom>
                Your Conversations
            </Typography>
            <List>
                {conversations.map((conversation) => (
                    <Conversation key={conversation._id} conversation={conversation} />
                ))}
            </List>
        </div>
    );
};

export default ConversationsPage;
JavaScript
// src/components/Notifications.jsx

import Notifications from "@mui/icons-material/Notifications";
import {
    Badge,
    Divider,
    IconButton,
    Menu,
    MenuItem,
    Typography,
} from "@mui/material";
import axios from "axios";
import React, { useEffect, useState } from "react";

const NotificationIcon = () => {
    const [notifications, setNotifications] = useState([]);
    const [anchorEl, setAnchorEl] = useState(null);
    const open = Boolean(anchorEl);

    useEffect(() => {
        const fetchNotifications = async () => {
            try {
                const response = await axios.get("http://localhost:3001/api/notifies");
                setNotifications(response.data.notifies);
            } catch (error) {
                console.error("Error fetching notifications:", error);
            }
        };

        fetchNotifications();
    }, []);

    const handleClick = (event) => {
        setAnchorEl(event.currentTarget);
    };

    const handleClose = () => {
        setAnchorEl(null);
    };

    const handleMarkAsRead = async (id) => {
        try {
            await axios.put(`http://localhost:3001/api/notifications/$%7Bid%7D/read%60);
            setNotifications(
                notifications.map((notify) =>
                    notify?._id === id ? { ...notify, isRead: true } : notify
                )
            );
        } catch (error) {
            console.error("Error marking notification as read:", error);
        }
    };

    return (
        <>
            <IconButton color="inherit" onClick={handleClick}>
                <Badge
                    badgeContent={
                        notifications?.filter((notify) => !notify?.isRead).length
                    }
                    color="error"
                >
                    <Notifications />
                </Badge>
            </IconButton>
            <Menu
                anchorEl={anchorEl}
                open={open}
                onClose={handleClose}
                PaperProps={{
                    style: {
                        width: 400,
                        maxHeight: 500,
                    },
                }}
            >
                {notifications?.length ? (
                    notifications?.map((notify) => (
                        <MenuItem
                            key={notify?._id}
                            onClick={() => handleMarkAsRead(notify?._id)}
                        >
                            <Typography variant="body2">{notify?.text}</Typography>
                        </MenuItem>
                    ))
                ) : (
                    <MenuItem>
                        <Typography variant="body2">No notifications</Typography>
                    </MenuItem>
                )}
            </Menu>
        </>
    );
};

export default NotificationIcon;
JavaScript
// src/components/Navbar.jsx

import {
    AccountCircle,
    Notifications,
    Search
} from "@mui/icons-material";
import {
    AppBar,
    Avatar,
    Badge,
    Button,
    IconButton,
    InputBase,
    Menu,
    MenuItem,
    Toolbar,
    Typography
} from "@mui/material";
import { alpha, styled } from "@mui/material/styles";
import React, { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";

import NotificationIcon from "./Notifications";

const SearchBar = styled("div")(
    ({ theme }) => ({
        position: "relative",
        borderRadius: theme.shape.borderRadius,
        backgroundColor:
            alpha(theme.palette.common.white, 0.15),
        "&:hover": {
            backgroundColor:
                alpha(theme.palette.common.white, 0.25),
        },
        marginRight: theme.spacing(2),
        marginLeft: 0,
        width: "100%",
        [theme.breakpoints.up("sm")]: {
            marginLeft: theme.spacing(3),
            width: "auto",
        },
    }));

const SearchIconWrapper
    = styled("div")(({ theme }) => ({
        padding: theme.spacing(0, 2),
        height: "100%",
        position: "absolute",
        pointerEvents: "none",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
    }));

const StyledInputBase = styled(InputBase)(
    ({ theme }) => ({
        color: "inherit",
        "& .MuiInputBase-input": {
            padding: theme.spacing(1, 1, 1, 0),
            // vertical padding + font size from searchIcon
            paddingLeft: `calc(1em + ${theme.spacing(4)})`,
            transition: theme.transitions.create("width"),
            width: "100%",
            [theme.breakpoints.up("md")]: {
                width: "20ch",
            },
        },
    }));

const Navbar = () => {
    const [anchorEl, setAnchorEl] = useState(null);
    const [user, setUser] = useState(null);
    const navigate = useNavigate();

    useEffect(() => {
        const loggedUser = localStorage.getItem("user");
        if (loggedUser) {
            setUser(JSON.parse(loggedUser));

        }
    }, []);

    const handleProfileMenuOpen = (event) => {
        setAnchorEl(event.currentTarget);
    };

    const handleMenuClose = () => {
        setAnchorEl(null);
    };

    const handleLogout = () => {
        localStorage.removeItem("user"); // Remove user from localStorage
        localStorage.removeItem("token"); // Remove token from localStorage
        setUser(null); // Set user state to null
        navigate("/login"); // Redirect to login page
    };

    const isMenuOpen = Boolean(anchorEl);
    const menuId = "primary-search-account-menu";

    return (
        <AppBar position="static">
            <Toolbar>
                <Typography variant="h6" sx={{ flexGrow: 1 }}>
                    Social Media App
                </Typography>

                {/* Search Bar */}
                <SearchBar>
                    <SearchIconWrapper>
                        <Search />
                    </SearchIconWrapper>
                    <StyledInputBase
                        placeholder="Search…"
                        inputProps={{ 'aria-label': 'search' }}
                    />
                </SearchBar>

                {/* Navigation Links */}
                <Button color="inherit" component={Link} to="/">Feed</Button>
                <Button color="inherit" component={Link} to="/post">Create Post</Button>
                <Button color="inherit" component={Link} to="/user-posts">My Posts</Button>
                <Button color="inherit" component={Link} to="/saved-posts">Saved Posts</Button>

                {/* Notifications */}
                <IconButton color="inherit">
                    <Badge badgeContent={4} color="error">
                        <NotificationIcon />
                    </Badge>
                </IconButton>

                {user?.fullname ? (
                    <>
                        {/* User Profile Dropdown */}
                        <IconButton
                            edge="end"
                            aria-label="account of current user"
                            aria-controls={menuId}
                            aria-haspopup="true"
                            onClick={handleProfileMenuOpen}
                            color="inherit"
                        >
                            <AccountCircle />
                        </IconButton>

                        {/* Dropdown Menu */}
                        <Menu
                            anchorEl={anchorEl}
                            anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
                            id={menuId}
                            keepMounted
                            transformOrigin={{ vertical: 'top', horizontal: 'right' }}
                            open={isMenuOpen}
                            onClose={handleMenuClose}
                        >
                            <MenuItem component={Link} to={`/user/${user._id}`}>Profile</MenuItem>
                            <MenuItem component={Link} to="/settings">Settings</MenuItem>
                            <MenuItem onClick={handleLogout}>Logout</MenuItem>
                        </Menu>
                    </>
                ) : (
                    <Button color="inherit" component={Link} to="/login">Login</Button>
                )}
            </Toolbar>
        </AppBar>
    );
};

export default Navbar;
JavaScript
// src/components/Conversion.jsx

import { Avatar, ListItem, ListItemAvatar, ListItemText } from "@mui/material";
import React from "react";
import { Link } from "react-router-dom";

const Conversation = ({ conversation }) => {
    return (
        <ListItem component={Link} to={`/chat/${conversation._id}`} button>
            <ListItemAvatar>
                <Avatar
                    alt={conversation.recipients[0].username}
                    src={conversation.recipients[0].avatar}
                />
            </ListItemAvatar>
            <ListItemText
                primary={conversation.recipients[0].fullname}
                secondary={conversation.text}
            />
        </ListItem>
    );
};

export default Conversation;


Start your app using the below command:

npm run dev

Output:


Similar Reads