Ask the internet how to build a real-time collaborative app and you will be handed a shopping list: React for the frontend, Redis for pub/sub, a message broker for fan-out, perhaps Kafka if the person answering is feeling ambitious. I built TheySynced — a multiplayer whiteboard with live drawing, cursors and chat — to test how much of that list is actually load-bearing. The answer, at the scale most projects live at, is none of it.
The backend is Rust on Axum. A session is an entry in a DashMap — a concurrent hashmap — holding the drawing history, connected users and chat log. Fan-out is a Tokio broadcast channel per session: when a user draws a stroke, the server validates it, appends it to session state, and publishes it on the channel; every other WebSocket in that session receives it and renders immediately. That is the entire real-time architecture. No broker, no Redis, no queue — the async runtime and a channel primitive do the work those systems are usually hired for.
The frontend is deliberately framework-free: HTML5 canvas plus vanilla JavaScript, no build step at all. Pen, line, rectangle, circle, text and eraser tools; live labelled cursors; per-user undo that respects concurrent editing. Undo across multiple users is the subtle part — each action carries its author's ID, so undoing removes your last action rather than whoever drew most recently. It is a small design decision that prevents the most rage-inducing bug a shared canvas can have.
Authentication is JWT-backed, with user accounts in SurrealDB — the one piece of persistence in the system. Sessions themselves are intentionally ephemeral: when the last participant leaves, the session evaporates. That choice is also the project's honest limitation: a server restart clears live boards, and passwords are hashed with SHA-256 where a production system would use Argon2. Both facts are in the README, because a limitations section you have to discover yourself is a bug report waiting to happen.
The whole thing deploys as a single binary that serves its own static files. What the exercise sharpened for me is a conviction I now apply everywhere: most 'necessary' infrastructure is necessary at a scale most projects never reach. Start with the platform primitives — the async runtime, a channel, a hashmap — and add machinery only when measured numbers demand it. The repo is small enough to read in an evening, which was rather the point.