"ChatBoard" is a private clubhouse where visitors can view messages, but only registered members can post content and see creator information. Members enjoy a real-time chat system with public and custom rooms for immediate interaction.
This project started as a monolithic Node.js chat toy app and was redesigned as a distributed, security-focused system.
It now showcases four production-style engineering themes:
- Distributed infrastructure with NGINX load balancing and Dockerized multi-instance Node services.
- Horizontal WebSocket scaling with Redis Pub/Sub and shared online presence.
- High-read optimization with Redis write-through caching for room history.
- End-to-end encrypted chat payloads using browser-side Web Crypto, with ciphertext-only storage in MongoDB.
- Node.js
- Express
- Socket.IO
- Redis
- MongoDB + Mongoose
- NGINX
- Docker + Docker Compose
- Web Crypto API (browser)
The runtime topology is:
- NGINX container as reverse proxy and load balancer.
- Two backend app containers (
app1,app2) running the same Node.js service. - One Redis container used for:
- Socket.IO adapter Pub/Sub
- global online user state via Redis Sets
- message cache via Redis Lists
- MongoDB (external URI) for persistent user and message data.
Traffic flow:
- Browser connects to NGINX on port 80 or 8080.
- NGINX forwards HTTP and WebSocket traffic to either app instance using sticky sessions (
ip_hash). - Socket.IO Redis adapter propagates events across both app instances.
- Messages persist in MongoDB and are cached in Redis.
- Message content is encrypted in the browser before being sent.
- User authentication with passportJS.
- Live chat implemented using Socket.Io.
- Securing passwords using bcryptjs.
- Schema validation using Mongoose.
This project is intentionally local-demo focused and not deployed publicly.
- Create
.envin project root:
MONGO_URI='your_mongodb_connection_string'
# Optional backward-compatible alias:
# MONGODB_URI='your_mongodb_connection_string'- Build and start cluster:
docker compose up -d --build- Open app:
http://localhost:8080
-
Demo checklist:
-
Register two users in two different browsers (or browser + private tab).
-
Open chat room in both sessions.
-
Send messages both ways and verify both clients can read decrypted text.
-
Inspect MongoDB and confirm
messagefield stores encrypted payload, not plaintext. -
(Optional) Check Redis cache size for room history:
docker exec -it chat_redis redis-cli LLEN room:general:messagesWhat this protects:
- Database compromise exposure is reduced because message content at rest is encrypted.
- Backend operators do not need plaintext to store and relay messages.
Current practical limitation:
- Key envelope coverage is based on available recipients at send time.
- A user who was not included in older message envelopes may not decrypt older history.
Planned hardening path:
- Persist room membership key envelopes per user (server stores encrypted key blobs only).
- Allow member devices to recover room keys on login and decrypt full backlog.
