A lightweight, real-time anonymous chat room backend built with Express.js, Socket.IO, and MongoDB.
Rooms are temporary — they expire automatically and all associated messages are cleaned up. No accounts, no history, just ephemeral chat.
- 🔐 Anonymous rooms — create public or private chat rooms with a 6-character ID
- ⏳ Auto-expiry — rooms expire between 1–24 hours; a background job purges rooms and their messages
- 💬 Real-time messaging — powered by Socket.IO with delivery acknowledgements
- 🚫 Profanity filter — messages cleaned via
leo-profanity - 🛡️ Spam protection — per-socket rate limiter (max 3 messages/second)
- 📄 Paginated message history — REST endpoint for fetching past messages
- 📊 Live user counts — active socket count per room via Socket.IO adapter
- 👥 Total joined count — unique nickname count from message history
| Layer | Technology |
|---|---|
| Runtime | Node.js |
| Framework | Express.js v5 |
| Real-time | Socket.IO v4 |
| Database | MongoDB + Mongoose |
| Rate Limiting | express-rate-limit |
| Profanity Filter | leo-profanity |
- Node.js ≥ 18
- A running MongoDB instance (local or Atlas)
git clone <your-repo-url>
cd bok-bok-server
npm installCopy .env.example and fill in your values:
cp .env.example .env| Variable | Description | Default |
|---|---|---|
MONGODB_URI |
MongoDB connection string | (required) |
PORT |
HTTP server port | 4000 |
CORS_ORIGIN |
Allowed CORS origin | * |
# Development (with auto-reload)
npm run dev
# Production
npm startCreate a new room.
Body
{
"roomName": "My Room",
"expireHours": 2,
"isPrivate": false
}Response
{
"success": true,
"data": {
"roomName": "My Room",
"roomId": "a1b2c3",
"isPrivate": false,
"expiresAt": "2026-01-01T14:00:00.000Z"
}
}Rate limited to 5 rooms per IP per hour.
List all public rooms with live online count and total participants.
Response
{
"success": true,
"data": [
{
"roomName": "My Room",
"roomId": "a1b2c3",
"expiresAt": "...",
"userCount": 3,
"totalJoined": 12
}
]
}Get a single room by ID.
Response
{
"success": true,
"data": {
"roomName": "My Room",
"roomId": "a1b2c3",
"isPrivate": false,
"expiresAt": "...",
"totalJoined": 12
}
}Fetch paginated messages for a room (newest first).
Response
{
"success": true,
"data": {
"messages": [
{ "roomId": "a1b2c3", "senderName": "Alice", "text": "Hello!", "createdAt": "..." }
],
"pagination": { "page": 1, "limit": 50, "total": 120, "totalPages": 3 }
}
}Post a message to a room via REST (Socket.IO preferred for real-time use).
Body
{
"senderName": "Alice",
"text": "Hello!"
}Connect to the server at ws://localhost:4000.
| Event | Payload | Description |
|---|---|---|
join_room |
{ roomId, senderName } |
Join a room |
send_message |
{ roomId, senderName, text } |
Send a message (with ack callback) |
typing |
{ roomId, senderName } |
Notify others you're typing |
stop_typing |
{ roomId, senderName } |
Notify others you stopped typing |
| Event | Payload | Description |
|---|---|---|
receive_message |
{ roomId, senderName, text, createdAt } |
New message broadcast |
system_message |
{ text } |
User joined/left notifications |
room_users_count |
{ count } |
Updated live user count |
user_typing |
{ senderName } |
Who is typing (null = stopped) |
error |
{ message } |
Room not found or expired |
import { io } from 'socket.io-client';
const socket = io('http://localhost:4000');
socket.emit('join_room', { roomId: 'a1b2c3', senderName: 'Alice' });
socket.emit('send_message', { roomId: 'a1b2c3', senderName: 'Alice', text: 'Hey!' }, (ack) => {
console.log(ack); // { success: true, message: 'Delivered' }
});
socket.on('receive_message', (msg) => {
console.log(msg.senderName, ':', msg.text);
});| Field | Limit |
|---|---|
roomName |
Max 60 characters |
senderName |
Max 30 characters |
text |
Max 500 characters |
expireHours |
1 – 24 |
| Request body | Max 16 KB |
bok-bok-server/
├── index.js # Entry point, Express + Socket.IO setup
├── models/
│ ├── Room.js # Room schema with compound indexes
│ └── Message.js # Message schema with compound indexes
├── routes/
│ ├── rooms.js # Room CRUD endpoints
│ └── messages.js # Message fetch + post endpoints
└── utils/
└── cleanup.js # Background job: purges expired rooms & orphaned messages
ISC