Learn how to build a powerful notification system with Replit. Follow easy steps to send alerts, improve user engagement, and enhance your app’s experience.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A simple, reliable way to build a notification system on Replit is to create a small backend using Node.js with Express and connect it to your frontend via Socket.IO. Socket.IO allows realtime communication between the server and clients, which makes it perfect for sending live notifications. You’ll store events in-memory or in a small database (like SQLite or Replit’s built-in database), and whenever a new event happens, you’ll broadcast it to all connected users. This approach runs smoothly in Replit and works even when you refresh or have multiple collaborators connected.
You’ll have these files in your Replit project:
In your main index.js file, paste the following code. This file runs automatically when you click “Run” on Replit because package.json points to this file as the main entry.
// Import dependencies
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
// Setup app and server
const app = express();
const server = http.createServer(app);
const io = new Server(server);
// Serve static files
app.use(express.static('public'));
// Simple route check
app.get('/ping', (req, res) => {
res.send('Server is alive!');
});
// Listen for new connections from clients
io.on('connection', (socket) => {
console.log('A user connected:', socket.id);
// Example: respond when client requests a fake notification
socket.on('sendNotification', (data) => {
console.log('Notification received on server:', data);
// Broadcast to all connected clients
io.emit('newNotification', data);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
// Start the server using Replit PORT
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Explanation:
Create a new folder named public in your project’s root. Inside it, add two files: index.html and script.js.
<!DOCTYPE html>
<html>
<head>
<title>Replit Notifications</title>
</head>
<body>
<h2>Realtime Notifications</h2>
<button id="notifyBtn">Send Test Notification</button>
<ul id="notificationList"></ul>
<!-- Socket.io client library -->
<script src="/socket.io/socket.io.js"></script>
<script src="script.js"></script>
</body>
</html>
// Connect to Socket.IO server
const socket = io();
// Elements from the DOM
const notifyBtn = document.getElementById('notifyBtn');
const notificationList = document.getElementById('notificationList');
// When the button is clicked, send a new notification event to server
notifyBtn.addEventListener('click', () => {
const newNotif = { text: 'Hello! This is a real-time notification.' };
socket.emit('sendNotification', newNotif);
});
// When the server sends a new notification, display it
socket.on('newNotification', (data) => {
const li = document.createElement('li');
li.textContent = data.text;
notificationList.appendChild(li);
});
<link rel="stylesheet" href="style.css">.
This setup is fully compatible with Replit’s built-in hosting, collaboration, and automatic restarts. It’s lightweight, uses only standard Express + Socket.IO, and can be extended later for production APIs or databases easily.
const express = require("express");
const { v4: uuid } = require("uuid");
const app = express();
app.use(express.json());
let userNotifications = {}; // key: userId, value: array of notifications
function createNotification(userId, message, type = "info") {
const note = {
id: uuid(),
message,
type,
read: false,
createdAt: new Date().toISOString(),
};
if (!userNotifications[userId]) userNotifications[userId] = [];
userNotifications[userId].unshift(note);
return note;
}
app.post("/notify", (req, res) => {
const { userId, message, type } = req.body;
if (!userId || !message) return res.status(400).json({ error: "Missing fields" });
const note = createNotification(userId, message, type);
res.json(note);
});
app.get("/notifications/:userId", (req, res) => {
const userId = req.params.userId;
res.json(userNotifications[userId] || []);
});
app.patch("/notifications/:userId/:id/read", (req, res) => {
const { userId, id } = req.params;
const userNotes = userNotifications[userId];
if (!userNotes) return res.status(404).json({ error: "User not found" });
const note = userNotes.find(n => n.id === id);
if (!note) return res.status(404).json({ error: "Notification not found" });
note.read = true;
res.json(note);
});
app.listen(3000, () => console.log("Notification API running on port 3000"));
const express = require("express");
const axios = require("axios");
const dotenv = require("dotenv");
dotenv.config();
const app = express();
app.use(express.json());
// Example: send notification using an external service (Discord Webhook)
async function sendExternalNotification(userId, message) {
const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
if (!webhookUrl) {
throw new Error("Missing Discord webhook URL");
}
const payload = {
content: `🔔 Notification for <@${userId}>: ${message}`,
};
return await axios.post(webhookUrl, payload);
}
app.post("/notify-external", async (req, res) => {
const { userId, message } = req.body;
if (!userId || !message) return res.status(400).json({ error: "Missing parameters" });
try {
await sendExternalNotification(userId, message);
res.json({ success: true, deliveredTo: "Discord" });
} catch (err) {
console.error("Error delivering notification:", err.message);
res.status(500).json({ error: "Failed to deliver notification" });
}
});
app.listen(3000, () => console.log("External notification endpoint running on port 3000"));
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: "\*" }
});
app.use(express.json());
let activeUsers = new Map(); // userId -> socketId
let pendingNotifications = {}; // userId -> [notification objects]
io.on("connection", (socket) => {
socket.on("register", (userId) => {
activeUsers.set(userId, socket.id);
if (pendingNotifications[userId]) {
pendingNotifications[userId].forEach(note => {
socket.emit("notification", note);
});
delete pendingNotifications[userId];
}
});
socket.on("disconnect", () => {
for (let [userId, id] of activeUsers.entries()) {
if (id === socket.id) {
activeUsers.delete(userId);
break;
}
}
});
});
app.post("/send-notification", (req, res) => {
const { userId, message, type } = req.body;
if (!userId || !message) return res.status(400).json({ error: "Missing fields" });
const notification = {
id: Date.now().toString(),
message,
type: type || "info",
sentAt: new Date().toISOString()
};
const socketId = activeUsers.get(userId);
if (socketId) {
io.to(socketId).emit("notification", notification);
} else {
if (!pendingNotifications[userId]) pendingNotifications[userId] = [];
pendingNotifications[userId].push(notification);
}
res.json({ delivered: !!socketId });
});
server.listen(3000, () => console.log("Real-time notification system is running"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A solid notification system on Replit usually means sending real-time or queued messages (like in-app alerts, emails, or browser push notifications) from your backend (Node.js or Python) to users. The key is to keep the architecture simple, reliable, and aligned with Replit’s limits — Replit Repls can sleep when inactive, so you should use an external service (like Upstash Redis, Firebase, or a hosted database) to persist notification data, and a lightweight frontend system (like React or plain JS) to display them. The simplest working pattern: store notifications in your database, use WebSocket or polling to deliver new ones, and update the client UI when new notifications arrive.
Inside Replit, your main backend file (often called index.js or server.js) is where you initialize the Express server. We’ll add a new route for saving notifications. If you don’t have Express installed yet, run this in the shell:
npm install express
Then, in your index.js file:
// index.js
const express = require("express")
const bodyParser = require("body-parser")
const app = express()
const PORT = process.env.PORT || 3000
app.use(bodyParser.json())
// temporary "in-memory" notifications store
// for production, you should use a real DB (like Replit DB, MongoDB Atlas, or Upstash Redis)
let notifications = []
// endpoint to add new notification
app.post("/api/notify", (req, res) => {
const { message } = req.body
if (!message) return res.status(400).json({ error: "Message is required" })
const note = { id: Date.now(), message }
notifications.push(note)
res.json({ success: true, note })
})
// endpoint to get notifications
app.get("/api/notifications", (req, res) => {
res.json(notifications)
})
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
This creates two HTTP endpoints: one to save a notification, and one to fetch all notifications. For real-world usage, replace the in-memory array with a Replit DB or Redis instance so data persists across restarts.
Using Replit’s built-in DB is simplest for small projects. Create a file named db.js in the root of your Repl, with this:
// db.js
const Database = require("@replit/database")
const db = new Database()
module.exports = db
Then update your index.js to store notifications persistently:
// replace the in-memory array logic
const db = require("./db")
app.post("/api/notify", async (req, res) => {
const { message } = req.body
const note = { id: Date.now(), message }
await db.set(`note_${note.id}`, note)
res.json({ success: true, note })
})
app.get("/api/notifications", async (req, res) => {
const keys = await db.list("note_")
const allNotes = []
for (let key of keys) {
const item = await db.get(key)
allNotes.push(item)
}
res.json(allNotes)
})
Replit DB persists automatically, so your notifications won’t disappear when the Repl restarts.
This lets you push notifications to the browser without refreshing. Install Socket.io:
npm install socket.io
Then modify your index.js (add this after app.listen()):
const http = require("http").Server(app)
const io = require("socket.io")(http, {
cors: { origin: "*" } // simple CORS for dev
})
// Listen for client connections
io.on("connection", (socket) => {
console.log("User connected", socket.id)
socket.on("disconnect", () => {
console.log("User disconnected", socket.id)
})
})
// Broadcast new notification after it’s created
app.post("/api/notify", async (req, res) => {
const { message } = req.body
const note = { id: Date.now(), message }
await db.set(`note_${note.id}`, note)
io.emit("new_notification", note) // send to all connected clients
res.json({ success: true, note })
})
http.listen(PORT, () => console.log(`Server with Socket.io running on port ${PORT}`))
Now your backend broadcasts notifications in real time to all open browsers.
If you have a public folder for your frontend (like public/index.html), insert this in the HTML body:
<div id="notifications"></div>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script>
const socket = io() // connects automatically to the same origin
const container = document.getElementById("notifications")
socket.on("new_notification", (note) => {
const div = document.createElement("div")
div.textContent = "🔔 " + note.message
container.prepend(div)
})
</script>
This will instantly show each new notification without reloading the page.
If your notifications include third-party services (like email via SendGrid, or push via Firebase), never hardcode keys. In Replit, set them in the Secrets tab (lock icon on left sidebar). For example:
// config.js
module.exports = {
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY
}
Then access them from your backend normally. Secrets stay out of your public code and are safe from exposure.
console.log() liberally to debug.
On Replit, the most reliable notification system uses a simple Express server, Replit DB for persistence, and Socket.io for real-time delivery. This approach stays lightweight, works with Replit’s limitations, and runs stably even as you scale to moderate traffic. You can later extend it with email, mobile, or push APIs—all while following the same principles: persist data, emit updates, and never rely solely on in-memory storage.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.