How to Design a Real-Time Chat System with MongoDB and Express.js (2025 Guide)
📱 “Can you build a chat app?”
That’s a question you’ve probably heard—or Googled—if you’re diving into full-stack development.
Here’s the catch: Chat apps look easy on the outside, but under the hood, they need to be real-time, secure, scalable, and efficiently structured in the database.
Whether you're building a Slack-style team chat, a social DMs feature, or a customer support inbox, you’ll need a solid plan.
In this post, you’ll learn how to design a real-time chat system using:
✅ MongoDB for storing messages and user metadata
✅ Express.js for handling the API
✅ Socket.IO for real-time messaging
We’ll walk through schema design, API structure, real-time event handling, and basic authentication—step by step.
⚠️ This is NOT just theory. Everything here is designed to help you ship faster and avoid common architecture mistakes.
🧱 Key Features of Our Chat System
- 🔒 User Authentication
- 👥 1-to-1 and Group Chats
- 💬 Message History
- 🟢 Online/Offline Status
- 📡 Real-Time Messaging with Socket.IO
- 📦 Efficient MongoDB Schema Design
🗃️ Step 1: Database Schema Design (MongoDB)
🧍 Users Collection
{
_id: ObjectId,
username: "john_doe",
email: "john@example.com",
password: "<hashed>",
avatarUrl: "/img/1.jpg",
status: "online" | "offline",
lastSeen: ISODate
}
💬 Messages Collection
{
_id: ObjectId,
senderId: ObjectId,
chatId: ObjectId,
text: "Hey, how are you?",
readBy: [ObjectId], // users who have read this message
createdAt: ISODate
}
👥 Chats Collection
{
_id: ObjectId,
name: "Team Alpha", // for group chats
isGroup: true,
participants: [ObjectId], // user IDs
lastMessage: ObjectId, // reference to last message
createdAt: ISODate
}
💡 Pro Tip: Store
lastMessageinside thechatdocument for performance, especially for chat previews.
⚙️ Step 2: Setting Up Express.js API Routes
🧑💻 User Routes
| Method | Route | Description |
|---|---|---|
| POST | /api/register |
Register a new user |
| POST | /api/login |
Authenticate + JWT |
| GET | /api/users |
List users |
📬 Chat Routes
| Method | Route | Description |
|---|---|---|
| POST | /api/chats |
Create 1-on-1 or group chat |
| GET | /api/chats/:userId |
Get user’s chats |
| POST | /api/chats/:id/message |
Send a message to a chat |
| GET | /api/chats/:id |
Get message history |
📡 Step 3: Adding Real-Time Messaging with Socket.IO
🔌 Setup Server with Socket.IO
const http = require('http');
const socketIO = require('socket.io');
const server = http.createServer(app);
const io = socketIO(server, {
cors: { origin: "*" }
});
👂 Listen for Events
io.on('connection', socket => {
console.log("User connected:", socket.id);
socket.on('joinChat', (chatId) => {
socket.join(chatId);
});
socket.on('sendMessage', async ({ chatId, senderId, text }) => {
const message = await Message.create({ chatId, senderId, text });
io.to(chatId).emit('receiveMessage', message);
});
socket.on('disconnect', () => {
console.log("User disconnected");
});
});
🔄 Clients join chat rooms using
join(chatId)so messages broadcast only to relevant users.
🛡️ Step 4: Authentication with JWT
🔐 Register & Login (Express)
const jwt = require('jsonwebtoken');
const login = async (req, res) => {
const user = await User.findOne({ email: req.body.email });
const isMatch = await bcrypt.compare(req.body.password, user.password);
if (!isMatch) return res.status(401).send("Invalid credentials");
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "1d" });
res.json({ token });
};
🔐 Middleware to Protect Routes
const auth = (req, res, next) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).send("No token");
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch {
res.status(401).send("Invalid token");
}
};
📊 Bonus: Track Online/Offline Users
🔴 Use Socket.IO for Presence
const onlineUsers = new Map();
io.on('connection', socket => {
const userId = socket.handshake.query.userId;
onlineUsers.set(userId, socket.id);
socket.on('disconnect', () => {
onlineUsers.delete(userId);
});
});
🟢 Broadcast Online Users
io.emit('onlineUsers', Array.from(onlineUsers.keys()));
📌 You can use this to show green dots beside user avatars, or trigger "user is typing" indicators.
🔁 Optional: Typing Indicators, Read Receipts
✍️ Typing Status
socket.on('typing', ({ chatId, userId }) => {
socket.to(chatId).emit('typing', userId);
});
✅ Mark as Read
socket.on('markAsRead', async ({ messageId, userId }) => {
await Message.updateOne({ _id: messageId }, { $addToSet: { readBy: userId } });
});
🔚 Conclusion
Designing a chat system isn’t just about throwing messages into a database. You need clear structure, secure auth, and real-time interactions that scale.
Let’s recap:
✅ MongoDB handles flexible user/message data
✅ Express.js provides a clean API
✅ Socket.IO keeps everything real-time
✅ JWT keeps users secure
✅ The schema supports 1-on-1, groups, history, and presence
🎯 Next Step: Try building this system with basic UI using React or Vue, and hook into Socket.IO for real-time messages.
🙋♂️ FAQ
Q1: Should I use WebSockets or Socket.IO?
A: Socket.IO is built on top of WebSockets and adds features like reconnection, rooms, and fallback—perfect for beginners.
Q2: Where should I store chat messages?
A: Use MongoDB. For massive scale, archive old messages to a cold-storage collection.
Q3: How do I deploy this in production?
A: Use NGINX + PM2 for Express, and Redis Adapter for scaling Socket.IO across multiple instances.
What would you build with this?
Would you add voice messages? File sharing? Emoji reactions?
Drop your ideas in the comments—let’s brainstorm the perfect chat experience. 👇
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
🛠 Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Flask Blog CMS with Admin Dashboard — Python Source Code
A Flask-based blogging website with a built-in admin dashboard. Manage articles, images, categories, tags and featured p...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.