A real-time collaborative whiteboard where multiple users can draw and type on a shared canvas simultaneously. Changes sync instantly across all connected clients using yjs CRDTs over WebSocket.
- Real-time sync — draw and type; all peers see changes instantly
- Cursor presence — see other users' cursors with name labels
- Persistent sessions — canvas state saved to MongoDB; restored on reconnect
- Session sharing — invite others via a 6-character share code
- Auth — HTTP Basic Auth guards all REST endpoints
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite |
| Canvas | fabric.js v5 |
| Real-time | yjs (CRDT), y-websocket |
| Routing | react-router-dom v6 |
| Backend | Node.js, Express, TypeScript |
| Persistence | MongoDB (snapshots), Redis (active sessions) |
| Infrastructure | podman-compose |
- Podman + podman-compose
- Or Docker + docker-compose (drop-in compatible)
git clone <repo-url>
cd real_time_collaborativeCopy and edit the env files (optional — defaults work out of the box):
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.envpodman-compose up --buildThis starts:
- Frontend at http://localhost:3000
- Backend at http://localhost:4000
- MongoDB at localhost:27017
- Redis at localhost:6379
Navigate to http://localhost:3000 and sign in with the default credentials:
| Username | Password |
|---|---|
admin |
password |
- Sign in with your username and password
- Create a session — give it a name; you're taken straight to the canvas
- Invite others — share the 6-character code shown in the top bar
- Join a session — enter a share code on the sessions page
- Draw — use the Draw tool (pencil) or Text tool (click to place text)
- Collaborate — open the same session in another tab or browser to see real-time sync
| Variable | Default | Description |
|---|---|---|
PORT |
4000 |
HTTP + WebSocket server port |
MONGODB_URL |
mongodb://localhost:27017/realtime-canvas |
MongoDB connection string |
REDIS_URL |
redis://localhost:6379 |
Redis connection string |
AUTH_USERNAME |
admin |
Single-user auth username |
AUTH_PASSWORD |
password |
Single-user auth password |
AUTH_USERS |
— | Multi-user auth: user1:pass1,user2:pass2 |
| Variable | Default | Description |
|---|---|---|
VITE_API_URL |
http://localhost:4000 |
Backend REST API base URL |
VITE_WS_URL |
ws://localhost:4000 |
Backend WebSocket base URL |
VITE_BACKEND_URL |
http://localhost:4000 |
Backend URL for Vite dev proxy |
real_time_collaborative/
├── podman-compose.yml
├── frontend/
│ ├── Containerfile
│ ├── .env.example
│ └── src/
│ ├── api/sessions.ts # REST client
│ ├── context/AuthContext.tsx # Auth state + localStorage
│ ├── pages/
│ │ ├── LoginPage.tsx # Sign-in screen
│ │ ├── SessionsPage.tsx # Create / join / list sessions
│ │ └── CanvasPage.tsx # Canvas + presence + share code
│ ├── components/
│ │ ├── Canvas.tsx # fabric.js + yjs binding
│ │ ├── CursorLayer.tsx # Remote cursor overlay
│ │ └── Toolbar.tsx # Draw / text mode switch
│ └── hooks/
│ ├── useYjs.ts # Y.Doc + WebSocket provider
│ └── useAwareness.ts # Cursor presence (yjs Awareness)
└── backend/
├── Containerfile
├── .env.example
└── src/
├── server.ts # Express + y-websocket setup
├── routes/sessions.ts # REST CRUD endpoints
├── middleware/basicAuth.ts # Auth guard
└── persistence/
├── mongodb.ts # Snapshot save/load
└── redis.ts # Active session tracking
All endpoints require HTTP Basic Auth.
| Method | Path | Body | Description |
|---|---|---|---|
POST |
/sessions |
{ name } |
Create a session → { id, shareCode } |
POST |
/sessions/join |
{ code } |
Join by share code → { id } |
GET |
/sessions |
— | List your sessions |
GET |
/sessions/:id |
— | Get session metadata |
GET |
/health |
— | Health check (no auth) |
WebSocket: ws://backend/yjs/:sessionId — yjs sync channel (no auth required)
podman-compose downTo also remove the MongoDB volume (clears all session data):
podman-compose down -v