Real-time collaborative whiteboard with physics, offline sync, and time travel
Infinite Canvas is a full-stack collaborative workspace that enables multiple users to create, manipulate, and interact with content on an infinite 2D surface in real-time. Built for hackathons and creative collaboration, it combines advanced whiteboard features with physics simulations, offline capabilities, and session replay.
The platform consists of two independently deployable apps, each with its own README:
real-time-canvas-web/β Next.js 16 with React 19, Fabric.js, Matter.js, and WebSocketreal-time-canvas-service/β Go with Gin framework, PostgreSQL, Redis, and Gorilla WebSocket
There is no root package.json/workspace tooling tying them together β each has its own dependency graph and lockfile, and is built/tested independently (see CI below).
- Real-time Collaboration: Multi-user sessions with live cursor tracking and presence
- Infinite Canvas: Smooth zoom, pan, and infinite scrolling with 100+ objects
- Rich Content Tools: Text, shapes, images, sticky notes, and audio recording
- Guest Authentication: Simple username-based login without sign-up requirements
- Responsive Design: Works seamlessly across desktop and mobile
- Physics Engine: Objects with gravity, collisions, attraction/repulsion (Matter.js)
- Mini-map + Radar: Real-time location tracking of collaborators
- Offline Support: Queue operations and sync when back online
- Time Travel: Replay entire session from beginning
- Export Options: PNG, SVG, and JSON formats
ASCII reference diagrams (high-level architecture, frontend architecture, data flow)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client (Browser) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Next.js Frontend (React 19) β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β β Canvas β β Tools β β Physics β β Time β β β
β β β Engine β β Panel β β Engine β β Travel β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β β Minimap β β Offline β β Export β β Real- β β β
β β β + Radar β β Sync β β Module β β time β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββ΄ββββββββββββββββ β
β β β β
β βΌ βΌ β
β βββββββββββββββββ βββββββββββββββββ β
β β HTTP REST β β WebSocket β β
β β (API Calls) β β (Live Sync) β β
β βββββββββββββββββ βββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Backend (Go + Gin) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β API Gateway Layer β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β β Auth β β Rooms β β Canvas β β Users β β β
β β β Routes β β Routes β β Routes β β Routes β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Service Layer β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β β Auth β β Room β β Canvas β β Sync β β β
β β β Service β β Service β β Service β β Service β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Repository Layer β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β β β Postgres β β Redis β β GORM β β WebSocketβ β β
β β β Repos β β Repos β β ORM β β Hub β β β
β β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Storage Layer β
β βββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ β
β β PostgreSQL β β Redis β β
β β βββββββββββββββββββββββ β β βββββββββββββββββββββββ β β
β β β Users β β β β Sessions β β β
β β β Rooms β β β β Pub/Sub Channels β β β
β β β Canvas Objects β β β β Room State Cache β β β
β β β Sync Events β β β β User Presence β β β
β β βββββββββββββββββββββββ β β βββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend Architecture β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Presentation Layer β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β Pages β β Components β β Layouts β β β
β β β (App Dir) β β (Reusable) β β (Structure) β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Logic Layer β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β Hooks β β Stores β β Services β β β
β β β (Custom) β β (Zustand) β β (Libs) β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Integration Layer β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β Fabric.js β β Matter.js β β Yjs β β β
β β β (Canvas) β β (Physics) β β (CRDT) β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β IndexedDB β β WebSocket β β REST API β β β
β β β (Offline) β β (Live) β β (HTTP) β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Flow Diagram β
β β
β User Action βββΊ Component βββΊ Hook/Store βββΊ Service/API β
β β β β
β β βΌ β
β β ββββββββββββββββββ β
β β β HTTP Request β β
β β β WebSocket β β
β β ββββββββββββββββββ β
β β β β
β β βΌ β
β β ββββββββββββββββββ β
β β β Backend β β
β β β Processing β β
β β ββββββββββββββββββ β
β β β β
β β βΌ β
β β ββββββββββββββββββ β
β β β Broadcast β β
β β β to Others β β
β β ββββββββββββββββββ β
β β β β
β β βΌ β
β βββββββββββββββββββββββββββββββββΊ ββββββββββββββββββ β
β β Update UI β β
β β (Realtime) β β
β ββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Technology | Purpose |
|---|---|
| Next.js 16 (App Router, Turbopack) | React framework |
| React 19 | UI library |
| TypeScript | Type safety |
| Tailwind CSS v4 | Styling and design system |
| Fabric.js v6 | Canvas rendering and object management |
| Matter.js | Physics simulation engine |
| Yjs | CRDT-based real-time collaboration |
| Zustand | State management |
| WebSocket | Real-time communication |
| IndexedDB (via localforage) | Offline operation queue |
ESLint (eslint-config-next) |
Linting |
| Technology | Purpose |
|---|---|
| Go 1.25+ | Backend language |
| Gin | Web framework |
| PostgreSQL 15+ | Primary database |
| Redis 7+ | Session management, pub/sub & rate limiting |
| GORM | ORM library |
| Gorilla WebSocket | WebSocket implementation |
| golang-migrate | Database migrations |
| go-redis | Redis client |
| AWS SDK v2 (S3) | Optional media storage backend (falls back to local disk) |
| JWT | Authentication |
| golangci-lint | Linting |
real-time-canvas/
βββ .github/workflows/ # CI: real-time-canvas-web-ci.yml, real-time-canvas-service-ci.yml
βββ docs/ # Diagrams and other project documentation
β βββ architecture.png # System architecture diagram (embedded above)
βββ real-time-canvas-web/ # Next.js frontend β see its own README
βββ real-time-canvas-service/ # Go backend β see its own README
βββ README.md # This file
real-time-canvas-web/
βββ app/ # Next.js App Router
β βββ (auth)/login/ # Login page
β βββ (canvas)/ # Canvas workspace
β β βββ page.tsx # Room launcher dashboard
β β βββ room/[roomId]/ # Individual canvas room
β βββ api/ # Route handlers (NextAuth, room proxying)
β βββ providers/ # Context providers
β βββ layout.tsx # Root layout
βββ components/
β βββ canvas/
β β βββ InfiniteCanvas/ # Core canvas (Core/Events/Renderer split)
β β βββ tools/ # Toolbar + Text/Shape/Image/StickyNote/Pencil/Audio tools
β β βββ objects/ # Fabric object wrappers (Shape/Text/Sticky/Image/Audio)
β β βββ dock/ # Dockable tool rail + color palette
β β βββ minimap/ # Radar minimap
β β βββ physics/ # Physics engine + Matter.js bridge + controls
β β βββ ZoomControls.tsx
β β βββ TimeTravelControls.tsx
β βββ collaboration/ # Presence, cursors, typing indicators
β βββ room/ # Room list/invite/info/create/join dialogs
β βββ export/ # PNG/SVG/JSON exporters
β βββ ui/ # Shared primitives (Button, Modal, Toast, ...)
βββ hooks/ # useCanvas, useWebSocket, usePhysics, useOfflineSync,
β # useTimeTravel, useRoom, useAuth, useCollaboration,
β # useMinimap, useZoomPan
βββ lib/
β βββ canvas/ # Fabric config, renderer, object factory, minimap renderer
β βββ websocket/ # WebSocket client, message handlers, event types
β βββ offline/ # IndexedDB, queue manager, sync engine
β βββ time-travel/ # Event store + replay engine
β βββ physics/ # Matter.js physics engine
β βββ yjs/ # CRDT collaboration (crdt/sync/provider)
β βββ theme/ # Theme provider (light/dark)
β βββ api/ # REST client
β βββ utils/ # coordinates, debounce, uuid
βββ store/ # Zustand stores: canvas, collaboration, drawing, export,
β # history, minimap, room, auth, user, websocket
βββ types/ # TypeScript type definitions
βββ config/ # Constants & environment config
βββ public/ # Static assets
βββ eslint.config.mjs
βββ next.config.ts
βββ package.json
βββ tailwind.config.ts
βββ tsconfig.json
βββ README.md
real-time-canvas-service/
βββ api/
β βββ routes.go # HTTP route definitions
βββ cmd/api/
β βββ main.go # Application entry point (wiring + startup)
βββ internal/
β βββ config/ # Config loading, DB connections, migration runner
β βββ handlers/ # auth, room, canvas, sync, media, websocket
β βββ middleware/ # auth (JWT), cors, logging, ratelimit (Redis-backed)
β βββ models/ # user, room, room_user, canvas_object, sync_event
β β βββ dto/ # Request/response DTOs
β βββ repository/
β β βββ postgres/ # user, room, canvas, sync repositories (GORM)
β β βββ redis/ # session repository, pub/sub
β βββ services/ # user, room, canvas, sync, media, physics
β βββ storage/ # Pluggable media storage: local disk or S3
β βββ websocket/ # hub, client, message types (+ hub concurrency tests)
βββ migrations/ # golang-migrate SQL migrations
βββ pkg/
β βββ database/ # Postgres/Redis connection helpers
β βββ jwt/ # Token generation/validation (+ tests)
β βββ redis/ # Shared Redis service (rate limiting, pub/sub)
β βββ utils/ # id_generator, validator
βββ uploads/ # Local media storage fallback (gitignored)
βββ go.mod
βββ go.sum
βββ .env.example
βββ README.md
Frontend:
- Node.js 22+
- npm (the repo ships a
package-lock.json, so usenpm ci/npm install)
Backend:
- Go 1.25+
- PostgreSQL 15+
- Redis 7+
# Clone the repository
git clone https://github.com/Oluwaseyi89/real-time-canvas.git
cd real-time-canvas
# βββ Frontend Setup βββ
cd real-time-canvas-web
npm install
cp .env.example .env
# βββ Backend Setup βββ
cd ../real-time-canvas-service
go mod download
cp .env.example .env
# Edit .env with your PostgreSQL and Redis credentials
nano .envSee each app's own README for the full, current list β real-time-canvas-web/.env.example and real-time-canvas-service/.env.example are the source of truth. At minimum:
NEXT_PUBLIC_APP_NAME=Infinite Canvas
NEXT_PUBLIC_API_URL=http://localhost:8080/api/v1
NEXT_PUBLIC_WS_URL=ws://localhost:8080/wsENVIRONMENT=development
PORT=8080
# PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=collaborative_canvas
DB_SSLMODE=disable
# Redis
REDIS_URL=redis://localhost:6379
# CORS β comma-separated origins allowed to make credentialed requests
ALLOWED_ORIGINS=http://localhost:3000
# Security
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
# Media uploads β leave S3_BUCKET empty to use local disk storage instead
S3_BUCKET=
LOCAL_UPLOAD_DIR=./uploads
LOCAL_UPLOAD_BASE_URL=http://localhost:8080/uploads
MEDIA_MAX_UPLOAD_MB=15# βββ Backend βββ
cd real-time-canvas-service
# Applies pending migrations on startup, then serves on :8080
go run cmd/api/main.go
# βββ Frontend βββ
cd ../real-time-canvas-web
# Development mode
npm run dev
# Production build
npm run build
npm start| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Register new user |
| POST | /api/v1/auth/login |
Login user |
| POST | /api/v1/auth/guest |
Guest login |
| GET | /api/v1/auth/profile |
Get user profile |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/rooms |
Create room |
| GET | /api/v1/rooms |
Get user rooms |
| GET | /api/v1/rooms/:id |
Get room by ID |
| PUT | /api/v1/rooms/:id |
Update room |
| DELETE | /api/v1/rooms/:id |
Delete room |
| POST | /api/v1/rooms/:id/join |
Join room |
| POST | /api/v1/rooms/:id/leave |
Leave room |
| GET | /api/v1/rooms/:id/users |
Get room users |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/rooms/:id/objects |
Create object |
| GET | /api/v1/rooms/:id/objects |
Get room objects |
| GET | /api/v1/rooms/:id/objects/:objId |
Get object by ID |
| PUT | /api/v1/rooms/:id/objects/:objId |
Update object |
| DELETE | /api/v1/rooms/:id/objects/:objId |
Delete object |
| POST | /api/v1/rooms/:id/objects/batch |
Batch create objects |
| POST | /api/v1/rooms/:id/objects/clear |
Clear room objects |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/rooms/:id/events |
Record a sync event (time-travel/offline replay log) |
| GET | /api/v1/rooms/:id/events |
Fetch missed events since a version, for reconnect/replay |
| POST | /api/v1/rooms/:id/media |
Upload an image/audio attachment (local disk or S3) |
| Endpoint | Description |
|---|---|
/ws |
WebSocket connection endpoint (auth via ?token= query param) |
All routes above /api/v1/auth/* require a Bearer JWT. Auth endpoints are rate-limited per IP (20 req/min); all other /api/v1 routes are rate-limited per authenticated user (300 req/min), backed by Redis.
room:joinβ Join a roomroom:leaveβ Leave a roomuser:presenceβ Update presenceuser:cursorβ Update cursor positionuser:typingβ Update typing statusobject:createβ Create objectobject:updateβ Update objectobject:deleteβ Delete objectphysics:*β Physics events (throw, collision, attract, repel)
connection:ackβ Connection acknowledgmentroom:joinedβ Room joined successfullyroom:leftβ Room left successfullyuser:joinedβ User joined roomuser:leftβ User left roomuser:presenceβ User presence updateuser:cursorβ User cursor updateuser:typingβ User typing statusobject:createβ Object createdobject:updateβ Object updatedobject:deleteβ Object deletedcanvas:syncβ Canvas synchronizationphysics:*β Physics events
- Background:
#090d16(Dark space) - Canvas Background:
#0b0f19 - Primary:
#6366f1(Indigo) - Accent:
#06b6d4(Cyan) - Surface:
rgba(15, 23, 42, 0.75)(Glassmorphism)
All panels use a consistent glassmorphism style with:
backdrop-blureffects- Subtle border opacity
- Shadow layers
- Smooth transitions
- Primary font: System UI (SF Pro, Segoe UI)
- Mono font: For technical elements and code
- Font sizes: Scale from 10px to 48px
- Object Rendering: Fabric.js handles 100+ objects efficiently with viewport culling
- Physics: Matter.js runs at 60fps with configurable gravity and time scale
- WebSocket: Binary message formats for efficient data transfer
- Offline: IndexedDB for operation queue with batched sync
- Database Indexes: Optimized queries with proper indexing
- Redis Caching: Session and room state caching for fast retrieval
- Connection Pooling: Efficient database and Redis connection management
- Horizontal Scaling: WebSocket hubs can be scaled with Redis pub/sub
Each app has its own workflow under .github/workflows/, scoped to only run when that app's files change:
real-time-canvas-web-ci.ymlβ installs, type-checks (tsc --noEmit), builds (next build), and runs ESLint. Lint currently reports rather than blocks β seereal-time-canvas-web/README.mdfor why.real-time-canvas-service-ci.ymlβ builds, vets, runs the test suite with the race detector, and runsgolangci-lintas a required check.
Run the same checks locally before pushing β see the Quality & CI section in each app's README for exact commands.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run the relevant app's lint/type-check/build/test commands locally (see its README)
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request β CI will run automatically against it
No LICENSE file is currently published for this repository, so default copyright applies (all rights reserved) unless/until the maintainer adds one.
