← Back to all projects

VibeConnect

A real-time social feed where every like, comment, and post has to reach every connected client through two independent transports — REST and WebSocket — without the two ever disagreeing about the current state.

React Node.js Express MongoDB Socket.io JWT
VibeConnect

Problem

Socket.io and REST are two independent paths to the same data. The moment a like or comment can arrive over either one, two connected clients can end up disagreeing about what's true — one still showing the old count, the other showing the new one. The problem was making the feed feel real-time without ever letting the two transports drift apart, and without letting an unauthenticated client subscribe to a channel it has no business hearing.

Constraints

  • Every state-changing action had to be valid whether it arrived over REST or a socket event — no separate code paths with separate rules.
  • A client's identity had to be verified at the socket layer independently; inheriting trust from an HTTP session wasn't acceptable, since the socket handshake is a different connection entirely.
  • Reconnects (dropped tabs, network blips) had to converge on correct state without a bespoke sync protocol — no case for building a CRDT or operational-transform layer at this scale.

Architecture

React frontend · Node/Express REST API handling standard data and auth endpoints · a Socket.io layer running alongside Express for real-time events · MongoDB for persistence · JWT used for both the HTTP requests and the socket handshake.

Key Engineering Decision

JWT is verified at the Socket.io handshake itself — not inherited from the REST session — so a socket connection carries its own proof of identity before it's allowed to subscribe to any channel. On the data side, every write lands in MongoDB before it broadcasts, so a broadcast is always a notification that something already happened, never the thing that makes it true. If a broadcast is dropped, a page refresh still shows the correct state, because REST and the socket layer read from the same source.

Trade-offs

Socket.io's default in-memory adapter scopes real-time state to a single Node process — this holds cleanly at current scale but doesn't horizontally scale across multiple server instances without adding a shared adapter (e.g. Redis pub/sub) in front of it. Writing to MongoDB before broadcasting also means real-time delivery is bounded by write latency, not instant — a deliberate consistency-over-speed trade-off, not an oversight.

Lessons Learned

Broadcasting from the socket layer before the database write landed was the fastest way to get two clients disagreeing about state — writing first, then broadcasting, removed that entire class of bug. The other real lesson: authenticating a socket handshake is a genuinely different problem from authenticating an HTTP request, and treating it as "the same JWT check, just somewhere else" is exactly the kind of assumption that creates a security gap.