A real-time collaborative whiteboard — think self-hostable Excalidraw with room-based drawing, auth, and persistence. Built as a Turborepo monorepo with a Next.js frontend, a Bun HTTP API, and a separate Bun WebSocket server.
Live: your-domain.com · Source: github.com/NalinDalal/week-22-excalidraw
Most collaborative whiteboards either give up the hand-drawn Rough.js aesthetic for performance, or lock you into a closed cloud. Existing open-source alternatives like Excalidraw are client-side only — no real-time sync, no auth, no persistence. CoDraw fills that gap: it keeps the organic sketchy rendering, supports full real-time collaboration over WebSockets, and runs on modest infrastructure without depending on a vendor.
┌─────────────────────────────────────────────────────────┐
│ Nginx :80 / :443 │
└───────┬──────────────────────┬───────────────────────────┘
│ │
┌────▼────┐ ┌────▼────┐
│ Frontend│ │ HTTP │
│ Next.js │ │ Backend │
│ :3000 │ │ :3001 │
└────┬────┘ └────┬────┘
│ │
│ ┌──────▼──────────┐
│ │ PostgreSQL │
│ │ (Neon) │
│ └─────────────────┘
│
┌────▼────────────────────┐
│ WebSocket Backend │
│ :8080 │
└─────────────────────────┘
Three independent services behind Nginx:
- Frontend (
apps/frontend) — Next.js 15 / React 19. Canvas rendering with Rough.js, local state management, WebSocket client, auto-save, export. - HTTP Backend (
apps/http-backend) — Bun + native Web API. Auth, room CRUD, session management, shape persistence with optimistic concurrency. - WebSocket Backend (
apps/ws-backend) — Bun + WebSocket server. Real-time shape diff broadcasting, cursor sync, chat, room management.
All three run under PM2 on a single EC2 instance (t3.small), with Neon providing managed PostgreSQL. CI/CD is a two-stage GitHub Actions pipeline: CI runs typecheck, lint, and build on every PR and push to main; on success, the deploy workflow SSHes into EC2, pulls latest code, rebuilds, runs migrations, and restarts PM2.
User draws on canvas
→ Frontend computes shape diff
→ WebSocket sends shape-diff message
→ WS Backend broadcasts to all users in room
→ Each frontend applies diff to local state
→ Debounced auto-save persists to Postgres via HTTP API
The frontend computes minimal diffs (added, modified, deleted shape IDs) — not full state — and sends only the delta over WebSocket. Each receiving client applies the diff immediately. Persistence is separate: the frontend debounces saves (1.5s) and sends the full state to the HTTP backend, which writes to Postgres with optimistic concurrency control.
On reconnection, the server sends the full authoritative state and the client replaces its local state. No delta replay needed.
- Tools: Select, Pencil, Rectangle, Ellipse, Diamond, Arrow, Line, Text, Image, Eraser
- Styling: Stroke color, fill color, roughness, opacity, stroke width
- Operations: Undo/Redo (delta-based stack, cap 100), Copy/Paste, Group/Ungroup, rubber-band multi-select
- Export: PNG, SVG, JSON export/import
- Room-based: create/join rooms via slug
- Real-time sync: diff-based WebSocket broadcasting
- Cursor sync: see other users' cursors
- Chat: per-room text chat
- Session-based auth: bcrypt password hashing, httpOnly cookies, server-side sessions
- Token revocation: logout invalidates session immediately
- WebSocket auth: short-lived WS tokens via
/auth/ws-token, heartbeat withre_auth - CORS: locked to
ALLOWED_ORIGINS - Rate limiting: sliding window on auth endpoints
- Input validation: room slug regex
[a-zA-Z0-9_-]
- Dirty-rect rendering: only redraws changed regions
- Layer caching: static / active / overlay layers
- Zoom-aware strokes: Rough.js stroke scales with zoom
- Debounced auto-save: 1.5s debounce with version checking
- Optimistic concurrency: 409 on version mismatch, client-side merge
- Theme: light/dark mode with localStorage persistence
- Touch: pinch-to-zoom, two-finger pan, double-tap text edit
- Keyboard shortcuts: V/H/R/O/T/E/A/D/P/I for tools, Ctrl+Z/Ctrl+Shift+Z, Escape, Delete
- Error boundaries: graceful fallback on canvas errors
- WebSocket reconnection: exponential backoff, 1s initial / 30s max
- CI/CD: GitHub Actions — typecheck, lint, build on every PR; deploy to EC2 on merge to main
- Infrastructure: Nginx reverse proxy, PM2 process manager, Neon Postgres
- Incident documentation: root-cause analysis for production issues
- Bun 1.2+
- PostgreSQL (local or Neon)
bun installcp .env.example .env| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
JWT_SECRET |
JWT signing secret (≥ 32 chars) |
ALLOWED_ORIGINS |
Comma-separated CORS origins |
NEXT_PUBLIC_HTTP_BACKEND |
HTTP API base URL |
NEXT_PUBLIC_WS_URL |
WebSocket URL |
bun run dev- Frontend:
http://localhost:3000 - HTTP backend:
http://localhost:3001 - WebSocket backend:
ws://localhost:8080
bun run build| Command | Description |
|---|---|
bun run dev |
Start all apps in development mode |
bun run build |
Build all packages and apps |
bun run lint |
Run ESLint across all workspaces |
bun run format |
Format code with Prettier |
codraw/
├── apps/
│ ├── frontend/
│ │ ├── app/ # Next.js App Router pages
│ │ ├── components/ # React UI components
│ │ └── draw/ # Canvas engine, rendering, input handling
│ ├── http-backend/
│ │ └── src/ # Auth, room CRUD, middleware, session
│ └── ws-backend/
│ └── src/ # WebSocket server, room broadcasting
├── packages/
│ ├── db/ # Prisma schema + migrations
│ ├── shapes/ # Shape type definitions
│ ├── ui/ # Shared React components
│ ├── common/ # Shared types, JWT utils, env config
│ ├── typescript-config/ # Shared tsconfig presets
│ └── eslint-config/ # Shared ESLint configs
├── deploy/ # Nginx + PM2 + deployment scripts
├── features.md # Detailed feature timeline
├── CoDraw.mdx # Full case study (portfolio)
└── CoDraw-Whiteboard.md # Whiteboard interview script
See deploy.md for a complete production deployment guide to a single AWS EC2 instance with Nginx, PM2, and Certbot.
Quick summary:
- Push to
mainbranch - CI runs typecheck + build + lint
- On success, deploy workflow SSHes into EC2 and redeploys automatically
See features.md for the full day-by-day development timeline (Jan 2025 → Aug 2026).
| Phase | Date | What changed |
|---|---|---|
| Bootstrap | Jan 2025 | Monorepo scaffolding, HTTP + WS servers, frontend skeleton |
| Core Canvas | Jul 2025 | Canvas engine, drawing tools, shapes, undo/redo, export |
| Auth & Rooms | Jul 2025 | Signup/signin, bcrypt, Prisma schema, room CRUD |
| Real-time Sync | Jul 2025 | WebSocket backend, diff-based broadcasting, reconnection |
| Production Hardening | Aug 2026 | Session table, httpOnly cookies, optimistic concurrency, CI/CD, incidents |
MIT