ChatRoom is a real-time, interest-based chat app built with Express and Socket.IO.
It is designed around anonymous use (no accounts), lightweight moderation, and private room support.
This README explains how the project is structured, how messages and rooms work, and how to run and customize it safely.
- Backend:
Node.js,Express,Socket.IO - Frontend: plain
HTML,CSS, and vanillaJavaScript - Moderation:
leo-profanityplus local custom word lists
server.js: main server (HTTP + WebSocket), room lifecycle, events, security/rate limitsmoderation.js: nickname/interest/message moderation helpers and emoji/violation checksmoderation.local.js: local-only custom moderation lists (ignored by git)moderation.local.example.js: template for local moderation configpublic/index.html: app UI structurepublic/app.js: all client behavior (join flow, room list, chat, private invites, E2EE handling)public/style.css: styling
- The browser loads
public/index.htmland opens a Socket.IO connection. - The client receives live room stats for the lobby.
- User joins by:
- choosing interests (auto-matching), or
- clicking a listed room, or
- joining/creating a private room.
- Messages are sent through the
messagesocket event, moderated/rate-limited on server, then broadcast to the room. - The client renders chat, users, typing state, reactions, and report actions in real time.
At startup, the server creates persistent default rooms:
generalmusicgamingcodingartmoviesbooksanime
These always appear in the lobby.
When a user joins by interests:
- server first tries to place them in an existing active matching room,
- then tries an empty matching default room,
- otherwise creates a new non-persistent public room.
Empty non-persistent rooms are cleaned up automatically.
Private rooms are created with short codes and can be:
- unlisted (invite-only), or
- listed (visible in lobby).
Room capacity is configurable per private room (bounded on server).
- Messages are delivered through Socket.IO room events.
- Server keeps a small per-room message buffer for reply/reaction targeting.
- Replies reference a message ID and include a preview payload.
- Reactions are toggled per user and emoji, then broadcast with aggregate counts.
Moderation is split between server checks (authoritative) and client-side filtering (visual comfort).
- Sanitizes nickname/interests.
- Rejects profane nicknames and tags.
- Applies message anti-spam controls:
- minimum send interval,
- 8 messages / 10s flood control (then escalation),
- duplicate message blocking,
- repeated-character spam blocking.
- Evaluates serious violation reports.
- Supports temporary IP-based mutes for abuse.
- Optional profanity masking toggle in UI.
- Runs only on displayed text (does not replace server moderation).
Sensitive or custom terms are loaded from moderation.local.js, which is git-ignored.
Shape of moderation.local.js:
module.exports = {
extraProfanityWords: [],
seriousViolations: [],
};Use moderation.local.example.js as the template.
Private unlisted rooms use a URL hash key flow for end-to-end encryption behavior on the client:
- sender encrypts message content in browser (AES-GCM via Web Crypto),
- server relays encrypted payload,
- recipient decrypts in browser when they have the same URL hash key.
Notes:
- the URL hash (
#key=...) is not sent as part of normal HTTP request path/query, - reporting still uses a message hash verification flow to validate claims.
Implemented controls include:
helmetsecurity headers and restrictive CSP- disabled
x-powered-by - conservative static asset headers
- event and action rate limiting (per-socket and per-IP)
- connection cap per IP
- room creation caps and memory pressure guardrails
- strict payload validation in socket handlers
- Node.js 18+ recommended
npm installnpm startServer default:
- host:
0.0.0.0 - port:
5000
Open http://localhost:5000.
PORT: server port (default5000)HOST: bind address (default0.0.0.0)BEHIND_PROXY: set totrueonly when running behind trusted reverse proxyHEAP_LIMIT_MB: memory threshold base for pressure checks (default512)
- State is in-memory only (rooms, sockets, message buffers, mutes).
- Restarting server clears active rooms and buffers.
- This is suitable for single-instance deployments or development.
- Horizontal scaling requires shared state (or sticky sessions + external coordination).
- No build step is required.
- Frontend assets are served directly from
public/. - Socket and moderation logic are currently hand-written and centralized.
If this grows, useful next refactors are:
- split socket handlers into modules (
join,message,rooms,reports) - move constants and limits into a config file
- add integration tests around socket event flows
- Wait for limiter window to reset.
- Check whether multiple browser tabs are open from the same IP.
- Make sure the full invite link (including
#key=...) is shared. - If hash is missing, room still works but messages may appear encrypted.
- Verify file is at project root next to
moderation.js. - Ensure it exports an object with
extraProfanityWordsand/orseriousViolations.