Skip to content
All projects

LiveBoard

A real-time collaborative Kanban board with presence, optimistic UI, version-based conflict handling and Redis for scaling across instances.

Type
Side project
Tech stack
  • NestJS
  • Socket.IO
  • React
  • Redis
Illustration of LiveBoard

The problem

A shared board is only useful if everyone sees the same thing. That's easy in a demo and hard in practice: two people edit the same card, a connection drops, or users end up on different server instances. None of that should make the UI flicker or quietly lose a change.

What it does

LiveBoard is a Kanban board where changes show up for everyone on the board straight away. You can create, edit, move and delete cards, see who else is there with avatars, live cursors and typing indicators, and follow every change in an activity log. Each board is its own WebSocket room with access control.

Changes apply in the UI immediately and roll back if the server rejects them. After a lost connection, the client fetches the events it missed and catches up.

Moving a card

  1. The client moves the card, updates the UI optimistically and emits card:move with the card's version.
  2. The gateway checks the JWT, board membership and the version.
  3. The change is saved in PostgreSQL in a transaction and the version goes up by one.
  4. The server broadcasts card:moved to the board room, and through Redis to the other instances.
  5. If the version was out of date, the sender gets card:rejected and resyncs.
  1. BrowsersSocket.IO clients
  2. NestJS gatewaysmultiple instances
  3. Redispub/sub adapter
  4. PostgreSQLPrisma
Clients connected to different gateway instances still see each other's changes, because events are shared through Redis.

Decisions

Every card carries a version number. Instead of last write wins, the server rejects updates based on an old version, so nobody silently overwrites a teammate's change.

Users log in over REST, and the JWT is checked when the socket connects. Each connection is then limited to the boards that user belongs to.

The Redis adapter was there from the start. Running docker compose up --scale api=2 shows that clients on different instances stay in sync. Cursor events are throttled, so presence stays smooth without flooding the connection.