Skip to content

pappukrs/Realtime-whiteboard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Realtime Collaborative Whiteboard

A production-grade real-time collaborative whiteboard web application similar to Google Docs + Canva + Miro, where multiple users can draw and edit a shared canvas in real time.


Tech Stack

Layer Technology
Frontend React + TypeScript, Vite, Konva.js, Zustand
UI Tailwind CSS, Lucide Icons, Shadcn/ui
Backend Node.js + Express (MVC), Socket.io
Database MongoDB (via Mongoose)
Pub/Sub Redis (via ioredis + @socket.io/redis-adapter)
Infrastructure Docker Compose (Redis, MongoDB)

Project Structure

canva-miro/
β”œβ”€β”€ docker-compose.yml          # Redis + MongoDB containers
β”‚
β”œβ”€β”€ backend/                    # Node.js Express Server (MVC)
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ index.ts            # Entry point β€” bootstraps DB, Redis, Socket, Server
β”‚   β”‚   β”œβ”€β”€ app.ts              # Express app β€” middleware, routes
β”‚   β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”‚   β”œβ”€β”€ database.ts     # MongoDB connection
β”‚   β”‚   β”‚   └── redis.ts        # Redis pub/sub client setup
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   └── Board.ts        # Mongoose schema for Board (roomId + elements)
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   └── board.service.ts  # Business logic (getBoardState, saveBoardState)
β”‚   β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”‚   └── board.controller.ts  # REST API handlers
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   └── board.routes.ts  # Express route definitions
β”‚   β”‚   └── socket/
β”‚   β”‚       └── socket.handler.ts  # Socket.io event handlers
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   └── package.json
β”‚
└── frontend/                   # React + Vite Application
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ main.tsx             # React DOM mount
    β”‚   β”œβ”€β”€ App.tsx              # Root component with router
    β”‚   β”œβ”€β”€ pages/
    β”‚   β”‚   └── Index.tsx        # Main page β€” room generation + Whiteboard mount
    β”‚   β”œβ”€β”€ components/
    β”‚   β”‚   β”œβ”€β”€ canvas/
    β”‚   β”‚   β”‚   └── Whiteboard.tsx  # Core canvas β€” drawing, tools, grid, zoom/pan
    β”‚   β”‚   └── toolbar/
    β”‚   β”‚       └── Toolbar.tsx  # Drawing tool palette + color pickers + undo/redo
    β”‚   β”œβ”€β”€ hooks/
    β”‚   β”‚   └── useSocket.ts    # WebSocket connection hook (emit/receive events)
    β”‚   β”œβ”€β”€ store/
    β”‚   β”‚   └── useBoardStore.ts  # Zustand state β€” elements, tools, history, view
    β”‚   └── types/
    β”‚       └── canvas.ts       # TypeScript types (ToolType, CanvasElement, Point, UserCursor)
    β”œβ”€β”€ index.html
    β”œβ”€β”€ vite.config.ts
    β”œβ”€β”€ tailwind.config.ts
    └── package.json

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        FRONTEND (React + Vite)                        β”‚
β”‚                                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Index   │───▢│ Whiteboard   │───▢│ Toolbar    β”‚    β”‚ useSocket  β”‚  β”‚
β”‚  β”‚ (Page)  β”‚    β”‚ (Canvas)     │◀──▢│ (UI)       β”‚    β”‚ (Hook)     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                        β”‚                                     β”‚         β”‚
β”‚                        β–Ό                                     β”‚         β”‚
β”‚               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                              β”‚         β”‚
β”‚               β”‚ useBoardStoreβ”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚               β”‚ (Zustand)    β”‚                                        β”‚
β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚ Socket.io (WebSocket)
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     BACKEND (Express + Socket.io)                    β”‚
β”‚                                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚ index.ts │───▢│ app.ts         │───▢│ board.routes.ts     β”‚      β”‚
β”‚  β”‚ (Boot)   β”‚    β”‚ (Express App)  β”‚    β”‚ board.controller.ts β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚       β”‚                                            β”‚                 β”‚
β”‚       β–Ό                                            β–Ό                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ socket.handler  │───────────────────▢│ board.service   β”‚         β”‚
β”‚  β”‚ (Realtime)      β”‚                    β”‚ (Business Logic)β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚           β”‚                                       β”‚                  β”‚
β”‚           β–Ό                                       β–Ό                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚ Redis Adapterβ”‚                       β”‚ MongoDB      β”‚            β”‚
β”‚  β”‚ (Pub/Sub)    β”‚                       β”‚ (Persistence)β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow

1. Room Joining

User opens app β†’ Index.tsx generates/retrieves roomId from localStorage
                β†’ Whiteboard mounts β†’ useSocket connects to backend
                β†’ socket.emit('join-room', roomId)
                β†’ Server fetches stored board from MongoDB via BoardService
                β†’ Server sends 'board-state' to the joining user
                β†’ useBoardStore.setElements() renders all elements

2. Drawing Flow (Real-time)

User draws on canvas β†’ handleMouseDown creates new CanvasElement
                     β†’ addElement() updates Zustand store (local render)
                     β†’ handleMouseUp β†’ emitDraw(element) sends to server
                     β†’ Server broadcasts 'draw-update' to other room users
                     β†’ Other users' useSocket receives 'draw-update'
                     β†’ addElement() renders the element on their canvas

3. Eraser Flow

User selects Eraser β†’ clicks on an element
                    β†’ removeElement(id) removes from local store
                    β†’ emitRemoveElement(id) sends to server
                    β†’ Server broadcasts 'element-removed' to room
                    β†’ Other users' removeElement(id) removes it

4. Text Tool Flow

User selects Text tool β†’ clicks on canvas
                       β†’ A textarea overlay appears at click position
                       β†’ User types text and presses Enter
                       β†’ handleTextSave() creates a CanvasElement of type 'text'
                       β†’ addElement() + emitDraw() syncs to all users

5. Undo/Redo Flow

Every draw/erase/clear action β†’ commitHistory() saves current elements[] snapshot
Ctrl+Z β†’ undo() reverts to previous snapshot β†’ emitSyncBoard(elements)
Ctrl+Y β†’ redo() moves forward in history    β†’ emitSyncBoard(elements)
Server broadcasts 'sync-board' β†’ other users setElements()

Socket Events Reference

Event Direction Payload Purpose
join-room Client β†’ Server roomId Join a whiteboard room
board-state Server β†’ Client elements[] Initial board state on join
draw Client β†’ Server { roomId, element } Send a new/updated element
draw-update Server β†’ Client element Broadcast element to other users
remove-element Client β†’ Server { roomId, id } Remove a specific element
element-removed Server β†’ Client id Broadcast element removal
clear-board Client β†’ Server { roomId } Clear entire board
board-cleared Server β†’ Client β€” Broadcast board clear
sync-board Bidirectional { roomId, elements[] } Full state sync (undo/redo)
cursor-move Client β†’ Server { roomId, cursor: {x,y} } Send cursor position
cursor-update Server β†’ Client { userId, cursor } Broadcast cursor to others
save-board Client β†’ Server { roomId, elements[] } Persist board to MongoDB

Zustand Store (useBoardStore)

State

Property Type Description
elements CanvasElement[] All canvas elements
cursors Record<string, UserCursor> Connected users' cursor positions
activeTool ToolType Currently selected tool
selectedElementId string | null Currently selected element
strokeColor string Current stroke color
fillColor string Current fill color
strokeWidth number Current stroke width
zoom number Canvas zoom level (0.1–5)
stagePos Point Canvas pan offset
showGrid boolean Grid visibility
history CanvasElement[][] Undo/redo history snapshots
historyStep number Current position in history

Available Tools

select Β· pan Β· pencil Β· rectangle Β· circle Β· line Β· arrow Β· text Β· eraser


Canvas Features (Implemented)

  • Infinite Canvas β€” Pan freely in any direction with the Hand tool or middle mouse button
  • Zoom β€” Mouse wheel zoom anchored to cursor position (0.1x – 5x)
  • Grid System β€” Dynamic grid that scales with zoom and extends with pan
  • Freehand Pen β€” Smooth freehand drawing with customizable stroke
  • Shapes β€” Rectangle, Circle, Line, Arrow with stroke + fill colors
  • Text Tool β€” Click to place, type, and render text elements on canvas
  • Eraser β€” Click on any element to remove it
  • Color Pickers β€” Separate stroke color and fill color selection
  • Stroke Width β€” Adjustable via range slider
  • Clear Canvas β€” Remove all elements with one click
  • Undo / Redo β€” Full history with Ctrl+Z / Ctrl+Y keyboard shortcuts
  • Real-time Sync β€” All actions broadcast to room participants instantly
  • Cursor Presence β€” See other users' cursors with labels in real-time
  • Board Persistence β€” Save/load board state to/from MongoDB

Getting Started

Prerequisites

  • Node.js (v18+)
  • Docker & Docker Compose

1. Start Infrastructure

docker compose up -d

This starts Redis (port 6379) and MongoDB (port 27017).

2. Start Backend

cd backend
npm install
npm run dev

Backend runs on http://localhost:4000.

3. Start Frontend

cd frontend
npm install
npm run dev

Frontend runs on http://localhost:5173.

4. Open in Browser

Navigate to http://localhost:5173. A unique room ID is auto-generated. Share the same room ID (via localStorage) across tabs to test multi-user collaboration.


Next Features to Build

Refer to plan.md for the full roadmap. Upcoming items include:

  • Selection & Multi-select with bounding box
  • Layer management (z-index ordering, lock, hide)
  • Image upload support
  • Sticky notes
  • Export to PNG/PDF
  • User authentication & room sharing via URL
  • Reconnection handling with state reconciliation

About

A production-grade real-time collaborative whiteboard web application similar to Google Docs + Canva + Miro, where multiple users can draw and edit a shared canvas in real time.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages