A real-time collaborative whiteboard: create rooms, share a link, and draw together with live user count.
- Real-time collaboration across multiple users in the same room
- Live user count per room
- Create/join rooms by name (slug) or numeric room ID
- Drawing tools: pencil (with width), rectangle, circle, eraser
- Color picker with 16 presets
- JWT-based auth with bcrypt password hashing
βββ apps/
β βββ excelidraw-frontend/ # Next.js 15 frontend (port 3002)
β βββ http-backend/ # Express REST API (port 3001)
β βββ ws-backend/ # WebSocket server (port 8081)
βββ packages/
β βββ db/ # Prisma schema + client
β βββ common/ # Shared Zod schemas
β βββ backend-common/ # Shared backend config (JWT_SECRET)
β βββ ui/ # Shared UI components
β βββ typescript-config/ # Shared tsconfig presets
Backends run directly from TypeScript via tsx β no compile step required for local dev.
Frontend: Next.js 15, React 19, TypeScript, Tailwind CSS, HTML5 Canvas
Backend: Express, ws, JWT, bcrypt
Database: PostgreSQL via Prisma
Monorepo: Turborepo + npm workspaces (tsx for backends)
- Node.js β₯ 18
- npm (bundled with Node) β do not use pnpm/yarn; only npm is supported
- A reachable PostgreSQL instance
From the repo root:
npm installThis installs every workspace (frontend, backends, shared packages) at once.
Copy the example env files and fill in real values.
cp packages/db/.env.example packages/db/.env
cp apps/http-backend/.env.example apps/http-backend/.env
cp apps/ws-backend/.env.example apps/ws-backend/.env
cp apps/excelidraw-frontend/.env.example apps/excelidraw-frontend/.env.localOn Windows PowerShell, use copy instead of cp.
Required values:
| File | Variables |
|---|---|
packages/db/.env |
DATABASE_URL |
apps/http-backend/.env |
PORT (3001), JWT_SECRET, DATABASE_URL |
apps/ws-backend/.env |
PORT (8081), JWT_SECRET, DATABASE_URL |
apps/excelidraw-frontend/.env.local |
NEXT_PUBLIC_HTTP_BACKEND, NEXT_PUBLIC_WS_URL |
Important:
JWT_SECRETmust match betweenhttp-backendandws-backend. There is no insecure fallback β missingJWT_SECRETwill crash the backend on startup.
npm run db:generate # generate Prisma client
npm run db:push # push schema to your Postgres databasenpm run devTurbo starts all three services with file watching:
- Frontend β http://localhost:3002
- HTTP API β http://localhost:3001
- WebSocket β ws://localhost:8081
To stop: Ctrl+C in the terminal.
npm run dev --workspace=excelidraw-frontend
npm run dev --workspace=http-backend
npm run dev --workspace=ws-backend- Go to http://localhost:3002 and sign up.
- Sign in; you'll be redirected to the Rooms page.
- Create New Room β enter a name β redirected to
/canvas/<roomId>. - Share the room ID (or slug) with another user; they can use Join by ID or Join by Link.
- Draw together β the user count bottom-center reflects everyone connected.
- Pencil β freehand, adjustable width
- Rectangle β click-drag
- Circle β click-drag
- Eraser β click a shape to remove it
- Color Picker β 16 presets
npm run dev # Run all apps in dev (Turborepo)
npm run build # Build all apps that have a build script (frontend)
npm run lint # Lint workspaces that have a lint script
npm run format # Prettier format
npm run db:generate # Prisma generate
npm run db:push # Prisma db push
npm run db:migrate # Prisma migrate dev- Passwords are hashed with bcrypt (10 rounds) before storage.
JWT_SECRETmust be set; the backend refuses to boot without it.- WebSocket connections are authenticated by a
?token=<jwt>query parameter. - JWT verification is wrapped in try/catch β invalid/expired tokens return 403 instead of crashing the server.
POST /signup { username, password, name } β { userId }
POST /signin { username, password } β { token }
POST /room (auth required) { name } β { roomId }
GET /room/:slug β { room }
GET /room/id/:id β { room }
GET /chats/:roomId β { messages }
// client β server
{ type: "join_room", roomId }
{ type: "leave_room", roomId }
{ type: "chat", roomId, message } // carries drawing add/delete payload
// server β client
{ type: "user_count", roomId, count, users }
{ type: "chat", roomId, message, userId }Drawing shapes are sent as chat messages with JSON payloads { action: "add" | "delete", β¦ } so that history can be rebuilt on reconnect via GET /chats/:roomId.
JWT_SECRET is not setβ createapps/http-backend/.envandapps/ws-backend/.envfrom the examples.Can't reach database serverβ verifyDATABASE_URLin every.envthat needs it (db package, http-backend, ws-backend) and that Postgres is running.- Port already in use β override
PORTin the relevant.env, and update the frontend'sNEXT_PUBLIC_HTTP_BACKEND/NEXT_PUBLIC_WS_URLaccordingly. - Changes to shared packages don't reflect β backends watch their own
src/; shared packages are loaded at import time. Restart the affected backend if you editpackages/commonorpackages/backend-common.
- Email: vineeta.garwal54@gmail.com


