A scalable, Excalidraw-like collaborative whiteboard built in full-stack Rust.
- Frontend: Yew + WASM + HTML5 Canvas (Trunk) — dev on port 3000
- Backend: Axum + Tokio + WebSockets on port 8080
- Production: Single server serves WASM UI + API from one port
- Shared core: Excalidraw-compatible elements, JSON I/O, reconciliation, undo/redo, SVG export, rough sketch paths
- Persistence: PostgreSQL (optional) with in-memory fallback
- Scaling: Redis pub/sub (optional) for multi-instance WebSocket broadcast
- Desktop: Tauri 2 shell in
apps/desktop
https://github.com/antcybersec/excaildraw
- Drawing tools: select/move, rectangle, ellipse, line, arrow, freehand, text, image, eraser
- Canvas: infinite pan/zoom, light/dark theme, rough/sketch strokes, selection highlights
- Layers panel: reorder elements, select from list
- History: undo/redo (Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y)
- Import/export:
.excalidrawJSON, SVG, PNG - Collaboration: WebSocket rooms, live sync, remote cursors
- E2E encryption: optional room password (AES-GCM)
- Offline: IndexedDB scene cache + pending update queue
- Auth: optional JWT; set
REQUIRE_AUTH=1to gate room creation
crates/
core/ Shared types, history, hit-test, SVG export, protocol, rough renderer
client/ WASM drawing UI, collab, crypto, IndexedDB offline
server/ REST API, WebSocket sync, PostgreSQL, JWT, Redis broker
apps/
desktop/ Tauri 2 native shell
docker/ Postgres + Redis compose + production Dockerfile
scripts/ build-release.sh for local production builds
- Rust 1.75+
wasm32-unknown-unknowntarget- Trunk
- Docker (optional, for PostgreSQL + Redis + production deploy)
rustup target add wasm32-unknown-unknown
cargo install trunk| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP listen port |
FRONTEND_DIST |
dist |
Path to Trunk dist/ for serving the WASM UI |
DATABASE_URL |
(none) | PostgreSQL connection string |
REDIS_URL |
(none) | Redis URL for cross-instance broadcast |
JWT_SECRET |
dev secret | HMAC secret for JWT signing |
REQUIRE_AUTH |
(off) | Set to 1 to require Bearer token on POST /api/rooms |
cargo run -p excaildraw-servercd crates/client && trunk serve --openPort 8080 is API-only in dev. The drawing UI is on 3000.
docker compose -f docker/docker-compose.yml up -d postgres redis
export DATABASE_URL=postgres://excaildraw:excaildraw@localhost:5432/excaildraw
export REDIS_URL=redis://localhost:6379- Optionally set a room password (E2E encryption)
- Click Live collab — creates a room and updates URL (
?room=...) - Share the URL (and password if used)
- Draw together — live sync + remote cursors
| Key | Tool |
|---|---|
| 1–9 | Tools (eraser = E) |
| Scroll | Zoom |
| Space + drag | Pan |
| Shift | Constrain shapes |
| Ctrl/Cmd + Z | Undo |
Builds WASM client + server into one image. Serves UI and API on port 8080.
docker compose -f docker/docker-compose.yml up -d --buildIncludes Postgres + Redis for persistent rooms and horizontal scaling.
./scripts/build-release.sh
FRONTEND_DIST=./dist ./target/release/excaildraw-serverPut nginx or Caddy in front for HTTPS:
server {
listen 443 ssl;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}WebSocket collab requires the Upgrade headers above.
cd crates/client && trunk build --release→ uploaddist/to Cloudflare Pages / Netlify / S3- Deploy server to Fly.io / Railway / Render with
DATABASE_URL+REDIS_URL - Set client API base via same-origin reverse proxy or configure CORS on server
- Set
JWT_SECRETto a strong random value - Set
DATABASE_URLfor room persistence - Set
REDIS_URLif running multiple server instances - Build with
--release(WASM + server) - Enable HTTPS (required for secure WebSockets in production)
- Set
REQUIRE_AUTH=1if you need gated room creation
cargo test -p excaildraw-core -p excaildraw-server -p excaildraw-client
cargo clippy -p excaildraw-core -p excaildraw-server -p excaildraw-client --all-targets -- -D warnings
cd crates/client && trunk build --releaseMIT