Back to Blogs
socket.iorealtimewebsocketredischat

Realtime Chat Architecture with Socket.IO

Apr 04, 20269 min readRealtime

Follow one message from authorization to storage, delivery, acknowledgement, and reconnect recovery.

Realtime Chat Architecture with Socket.IO cover
--

Connection is not membership

The socket handshake authenticates a user. It does not prove that the user may join every room they name. On each join, the server loads membership or evaluates permission, then attaches the socket to the room. A client-supplied user ID is never the identity source.

Membership can change while the socket remains open. For sensitive rooms, revocation needs a path that removes active sockets or rechecks authorization before write operations.

A message crosses five boundaries

The client creates a temporary ID and sends the room ID, content, and that ID. The server validates membership and payload, persists a message with a server sequence, then emits the canonical record. The acknowledgement maps the temporary ID to the stored ID so the sender can replace its pending item instead of rendering a duplicate.

Persist-before-broadcast gives history a clear source of truth. It adds a database write to the visible path, but recipients never see a message that disappears on refresh. For a chat product, that tradeoff is usually worth it.

A uniqueness constraint on sender and client message ID makes a network retry return the existing record. It also covers the case where the server saved the message but the acknowledgement never reached the client.

Scaling changes delivery, not ownership

With several Socket.IO instances, a Redis adapter forwards room events so users connected to different nodes receive the same broadcast. The database remains the owner of message history. Redis is the fanout path, not the record of what was said.

Presence is softer state. Heartbeats, disconnect timers, and multi-device sessions mean online is an estimate. Last seen and device-aware connection counts are often more honest than a single boolean.

Files take another route

I do not send large file bodies through the socket. The client uploads to object storage through an authorized endpoint or signed URL, the server validates the resulting metadata, and the chat message carries a reference to the attachment.

The authorization check still applies when the file is downloaded. An unguessable URL is not access control, and deleting a message may require a separate retention decision for the stored object.

Reconnect from a cursor

After reconnect, the client sends the last confirmed sequence for each active room. The server returns later messages in order, and the client merges them by canonical ID. This covers events missed during the gap without refetching an entire conversation.

The UI keeps pending, sent, delivered, and failed distinct. A timeout does not necessarily mean the message failed; it means the client does not know. Retrying with the same client message ID lets the server resolve that uncertainty safely.

Realtime feels reliable when disconnection is treated as an ordinary state. The live socket is only the fast path; persistence and cursor recovery are what make the conversation durable.