A free, scalable real-time chat application designed for 10,000+ concurrent users using open-source technologies. This architecture supports both group chats and peer-to-peer messaging without any subscription costs.
- Node.js + Express - Backend servers
- Socket.io - Real-time WebSocket communication
- Redis - Pub/Sub messaging & caching
- MongoDB - Message persistence
- RabbitMQ - Offline message queuing
- Docker - Containerization for easy scaling
- Nginx - Load balancing
graph TB
subgraph "Client Layer"
U1[User 1<br/>Web/Mobile Client]
U2[User 2<br/>Web/Mobile Client]
U3[User 3<br/>Web/Mobile Client]
U4[User 4<br/>Web/Mobile Client]
end
subgraph "Load Balancer"
LB[Nginx Load Balancer]
end
subgraph "Application Layer"
S1[Node.js Server 1<br/>Socket.io]
S2[Node.js Server 2<br/>Socket.io]
S3[Node.js Server N<br/>Socket.io]
end
subgraph "Message Broker"
REDIS[Redis Pub/Sub<br/>Message Broadcasting]
end
subgraph "Data Layer"
MONGO[(MongoDB<br/>Message Storage)]
RABBIT[RabbitMQ<br/>Offline Messages]
end
U1 -.->|WebSocket| LB
U2 -.->|WebSocket| LB
U3 -.->|WebSocket| LB
U4 -.->|WebSocket| LB
LB --> S1
LB --> S2
LB --> S3
S1 <--> REDIS
S2 <--> REDIS
S3 <--> REDIS
S1 --> MONGO
S2 --> MONGO
S3 --> MONGO
S1 <--> RABBIT
S2 <--> RABBIT
S3 <--> RABBIT
sequenceDiagram
participant U1 as User 1
participant S1 as Server 1
participant R as Redis Pub/Sub
participant M as MongoDB
participant S2 as Server 2
participant U2 as User 2
participant RQ as RabbitMQ
U1->>S1: 1. Send message "Hello everyone!"
S1->>R: 2. Publish message to channel
S1->>M: 3. Store message in database
R->>S1: 4. Broadcast to Server 1
R->>S2: 4. Broadcast to Server 2
S1->>U1: 5. Echo back to sender (optional)
alt User 2 is online
S2->>U2: 6. Deliver message instantly
else User 2 is offline
S2->>RQ: 6. Queue message for later
Note over RQ: Message queued until user comes online
RQ->>U2: 7. Deliver when user reconnects
end
sequenceDiagram
participant A as User A
participant S1 as Server 1
participant R as Redis
participant M as MongoDB
participant S2 as Server 2
participant B as User B
A->>S1: 1. Send personal message to User B
Note over S1: Create P2P room: "p2p_userA_userB"
S1->>R: 2. Publish to P2P channel
S1->>M: 3. Store with recipientId
R->>S2: 4. Broadcast to all servers
Note over S2: Check if User B is connected here
alt User B online on Server 2
S2->>B: 5. Deliver personal message
else User B offline
S2->>RabbitMQ: 5. Queue for offline delivery
end
// server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const redis = require('redis');
const mongoose = require('mongoose');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// Redis clients
const redisClient = redis.createClient();
const redisSubscriber = redis.createClient();
// Message schema
const messageSchema = {
roomId: String,
senderId: String,
recipientId: String, // Only for P2P messages
content: String,
messageType: String, // 'group' or 'personal'
timestamp: Date,
delivered: Boolean
};
// Socket connection handling
io.on('connection', (socket) => {
console.log(`User connected: ${socket.id}`);
// Join user to their personal room
socket.on('join', (userId) => {
socket.userId = userId;
socket.join(`user_${userId}`);
});
// Handle group messages
socket.on('group_message', async (data) => {
const message = {
roomId: data.roomId,
senderId: socket.userId,
content: data.content,
messageType: 'group',
timestamp: new Date()
};
// Publish to Redis
redisClient.publish('chat_messages', JSON.stringify(message));
// Store in MongoDB
await saveMessage(message);
});
// Handle personal messages
socket.on('personal_message', async (data) => {
const roomId = createP2PRoom(socket.userId, data.recipientId);
const message = {
roomId,
senderId: socket.userId,
recipientId: data.recipientId,
content: data.content,
messageType: 'personal',
timestamp: new Date()
};
// Publish to Redis
redisClient.publish('chat_messages', JSON.stringify(message));
// Store in MongoDB
await saveMessage(message);
});
});
// Redis subscription for receiving messages
redisSubscriber.subscribe('chat_messages');
redisSubscriber.on('message', (channel, data) => {
const message = JSON.parse(data);
if (message.messageType === 'group') {
// Broadcast to room
io.to(message.roomId).emit('new_message', message);
} else {
// Send to specific user
io.to(`user_${message.recipientId}`).emit('personal_message', message);
}
});
// Helper functions
const createP2PRoom = (userId1, userId2) => {
return `p2p_${[userId1, userId2].sort().join('_')}`;
};
const saveMessage = async (message) => {
// MongoDB save logic
await Message.create(message);
};# docker-compose.yml
version: '3.8'
services:
# Multiple Node.js servers
chat-server-1:
build: .
ports:
- "3001:3000"
environment:
- SERVER_ID=server-1
- REDIS_URL=redis://redis:6379
- MONGO_URL=mongodb://mongo:27017/chatdb
depends_on:
- redis
- mongo
- rabbitmq
chat-server-2:
build: .
ports:
- "3002:3000"
environment:
- SERVER_ID=server-2
- REDIS_URL=redis://redis:6379
- MONGO_URL=mongodb://mongo:27017/chatdb
depends_on:
- redis
- mongo
- rabbitmq
# Load balancer
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- chat-server-1
- chat-server-2
# Redis for pub/sub
redis:
image: redis:alpine
ports:
- "6379:6379"
# MongoDB for persistence
mongo:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
# RabbitMQ for offline messages
rabbitmq:
image: rabbitmq:management-alpine
ports:
- "5672:5672"
- "15672:15672"
volumes:
mongo_data:// Database cleanup to prevent overflow
const cleanupOldMessages = async () => {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
// Delete old messages
await Message.deleteMany({
timestamp: { $lt: thirtyDaysAgo }
});
console.log('Cleaned up messages older than 30 days');
};
// Run cleanup daily
setInterval(cleanupOldMessages, 24 * 60 * 60 * 1000);
// MongoDB indexes for performance
db.messages.createIndex({ "timestamp": 1 }, { expireAfterSeconds: 2592000 }); // TTL
db.messages.createIndex({ "roomId": 1, "timestamp": -1 });
db.messages.createIndex({ "senderId": 1, "timestamp": -1 });
db.messages.createIndex({ "recipientId": 1, "timestamp": -1 });graph LR
subgraph "1-100 Users"
A[1 Server + Redis + MongoDB]
end
subgraph "100-1000 Users"
B[2-3 Servers + Redis + MongoDB + Load Balancer]
end
subgraph "1000-5000 Users"
C[5-8 Servers + Redis Cluster + MongoDB Replica Set]
end
subgraph "5000-10000+ Users"
D[10+ Servers + Redis Cluster + MongoDB Sharding + CDN]
end
| Users | Servers | RAM per Server | CPU per Server | Total Cost |
|---|---|---|---|---|
| 0-100 | 1 | 512MB | 1 vCPU | FREE |
| 100-1K | 2-3 | 1GB | 1 vCPU | FREE |
| 1K-5K | 5-8 | 2GB | 2 vCPU | FREE |
| 5K-10K+ | 10+ | 4GB | 2 vCPU | $0-50/month |
Using free tiers from platforms like Railway, Render, Heroku, DigitalOcean credits, etc.
- Railway - Free tier with 512MB RAM
- Render - Free tier with 512MB RAM
- Fly.io - Free allowances
- Heroku - Limited free tier
- DigitalOcean - $200 student credits
- Google Cloud - $300 free credits
- AWS - Free tier for 12 months
- MongoDB Atlas - 512MB free cluster
- Redis Cloud - 30MB free tier
- ElephantSQL - 20MB PostgreSQL free
- PlanetScale - MySQL free tier
// MongoDB connection pooling
mongoose.connect(mongoUrl, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
});
// Redis connection pooling
const redisPool = new Redis.Cluster([
{ host: 'redis-1', port: 6379 },
{ host: 'redis-2', port: 6379 },
]);// Batch messages for better performance
const messageBatch = [];
const BATCH_SIZE = 100;
const BATCH_TIMEOUT = 1000; // 1 second
const processBatch = async () => {
if (messageBatch.length > 0) {
await Message.insertMany(messageBatch);
messageBatch.length = 0;
}
};
// Process batch every second or when full
setInterval(processBatch, BATCH_TIMEOUT);// Cache frequent data in Redis
const cacheUser = async (userId, userData) => {
await redisClient.setex(`user:${userId}`, 3600, JSON.stringify(userData));
};
const getCachedUser = async (userId) => {
const cached = await redisClient.get(`user:${userId}`);
return cached ? JSON.parse(cached) : null;
};const rateLimit = require('express-rate-limit');
const messageLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 30, // 30 messages per minute per IP
message: 'Too many messages, please slow down'
});
app.use('/api/messages', messageLimiter);const joi = require('joi');
const messageSchema = joi.object({
content: joi.string().max(1000).required(),
roomId: joi.string().required(),
recipientId: joi.string().when('messageType', {
is: 'personal',
then: joi.required()
})
});β Real-time messaging - Instant message delivery β Group chats - Multiple users in rooms β Private messages - Peer-to-peer communication β Offline message queuing - Messages delivered when user returns β Message persistence - Chat history stored β Horizontal scaling - Add more servers as needed β Cross-server communication - Users on different servers can chat β Auto cleanup - Prevents database overflow β Load balancing - Distribute users across servers β Zero subscription costs - 100% open source stack
# Clone and setup
git clone <your-repo>
cd scalable-chat-app
# Install dependencies
npm install
# Start with Docker
docker-compose up -d
# Or start individual services
npm run start:server1
npm run start:server2
npm run start:nginx# .env
REDIS_URL=redis://localhost:6379
MONGO_URL=mongodb://localhost:27017/chatdb
RABBITMQ_URL=amqp://localhost:5672
SERVER_PORT=3000
JWT_SECRET=your-secret-keyThis architecture is designed specifically for student projects and learning purposes:
- No subscription costs - Everything is open source
- Free hosting options - Use free tiers and student credits
- Scalable design - Can grow from 10 to 10,000+ users
- Industry-standard - Uses real-world technologies
- Well-documented - Easy to understand and modify
- Portfolio-ready - Impressive for job applications
Total monthly cost: $0-50 even for 10,000+ users!
This documentation provides a complete blueprint for building a scalable, production-ready chat application without breaking the bank. Perfect for students who want to build something impressive while learning industry-standard technologies.