Real-time Collaborative App
Part of the Full Stack Developer Roadmap
In short
Build a board that several people edit at once, with every change showing up live. It pulls together REST, WebSockets, a database and cache sync, which is exactly what interviewers love to dig into.
A shared, live-updating app (a collaborative list, board, or simple document) — bringing together WebSockets, state sync, and multi-user considerations across your whole stack.

Think of it like this
The shared fridge door
Flatmates stick notes on the fridge door: 'milk over', 'gas bill paid'. Everyone sees the same door, anyone can add or move a note, and it's still there tomorrow. Your app is that fridge door online: shared, live, and saved in a database.
Watch it happen
Build it in layers
1/6Tables for boards, members and cards, with column, position and version on each card. Seed one board.
The big ideas
What you need to know
Multi-user boards
Invite members by email. Only members can join a board's room or call its APIs.
Live card moves
Create, edit, move and delete cards, and everyone on the board sees it within a second.
Who's here
Show avatars of people viewing the board right now. Presence is short-lived, so keep it out of the database.
The server decides
Clients send changes, the server validates and saves them, then broadcasts the result. Never broadcast unsaved data.
See it in code
Walk through the code
1io.use(authenticateSocket); // sets socket.data.user from the session cookie2 3io.on("connection", (socket) => {4 socket.on("board:join", async (boardId: string) => {5 if (!(await isMember(socket.data.user.id, boardId))) return;6 socket.join(`board:${boardId}`);7 });8 9 socket.on("card:move", async ({ cardId, column }, ack) => {10 const card = await moveCard(socket.data.user.id, cardId, column); // checks access, bumps version11 if (!card) return ack({ ok: false });12 io.to(`board:${card.boardId}`).emit("card:moved", card);13 ack({ ok: true, card });14 });15});1/4Every socket is authenticated before any event handler runs.
A Socket.io server. authenticateSocket, isMember and moveCard are your own functions that reuse the same checks as your REST routes.
Save yourself the pain
Common mistakes
Oops
Broadcasting a change before it's saved to the database.
Do this instead
Save first, then emit the saved row. Otherwise a failed write leaves everyone's screen wrong.
Oops
Letting any socket join any room just by sending a board id.
Do this instead
Check membership on the server before socket.join, exactly as you would on a REST route.
Oops
Storing card order as array indexes that clash when two people move cards at once.
Do this instead
Give each card a position number and let the server's saved value win.
Your mission
First milestone: live cards in two tabs
Quick check
Two users drag the same card at nearly the same moment. What keeps every screen consistent?
Go deeper
- 1Show multiple users' changes appearing live without a page refresh.
- 2Handle two users editing the same item at nearly the same time.
- 3Add a simple presence indicator showing who else is currently connected.
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.