Real-time··7 min read

Real-Time Messaging at Scale with Socket.io: Rooms, Delivery, and Backpressure

Design notes from building a Socket.io messaging platform with file transfers, forwarding, and reactions on top of MySQL and MongoDB data models.

By Syed Aasim Shah

Model rooms around real entities

How should you structure rooms in a Socket.io messaging system?

Map rooms to durable domain entities — a conversation, an order, a support thread — not to transient UI state. Authenticate the socket on connection and authorise every join against the same rules as your HTTP API.

In the messaging platform I built at ItecExperts, each conversation was a room whose membership came from the database, not from whatever the client asked to join. The socket handshake carried the same auth token as the REST API, and room joins were checked against conversation membership server-side.

Keeping room identity tied to persistent entities means reconnects, multiple devices, and server restarts all resolve to the same place, and it keeps authorisation logic in one shared layer instead of being reimplemented for the socket path.

Persist first, then broadcast

How do you make real-time message delivery reliable?

Write the message to the database before emitting it, assign it a server-side ID and timestamp, and let clients fetch anything they missed by ID range on reconnect. The socket is a fast path, not the system of record.

Every message — including file transfers, forwards, and reactions — was persisted with a server-assigned ID before it was broadcast to the room. Clients treat the socket stream as an optimisation and fall back to a REST history call keyed on the last ID they saw whenever they reconnect.

That ordering makes delivery robust against dropped sockets and brief server outages: nothing that was accepted is lost, and duplicates are easy to collapse on the client because IDs are monotonic per conversation.

Plan for backpressure and fan-out

How do you handle backpressure in a Socket.io application?

Cap per-connection send queues, batch high-frequency events like typing and reactions, and use a Redis adapter so broadcasts fan out across instances instead of pinning everyone to one process.

Large rooms and chatty events — typing indicators, read receipts, reactions — can overwhelm a single Node process. Batching those events, dropping stale ones, and bounding each client's outbound buffer keeps one slow consumer from degrading the room.

Horizontal scaling uses the Redis adapter so any instance can accept a connection and still deliver to the whole room. MySQL and MongoDB models were tuned for the two dominant access patterns: appending to a conversation and loading a bounded window of recent history.

References

Related reading

Need this built? See services or start a project.

← Back to all posts