- PostgreSQL (via
pg) — replaces SQLite; async connection pool; same schema - Redis pub/sub (via
ioredis) — optional; whenREDIS_URLis set, Yjs updates are published toydoc:<docId>channel so all server instances relay them to their local clients - y-indexeddb — browser-side offline persistence; doc loads from IndexedDB instantly on open; edits queue offline and merge via CRDT on reconnect
- Docker Compose — includes postgres:16 + redis:7 services with health checks
- User opens doc → IndexedDB loads last known state instantly (no server needed)
- WebSocket connects in background → server state merges with local state via CRDT
- User goes offline → edits continue writing to IndexedDB and the Yjs doc in memory
- User comes back online → WebSocket reconnects, queued updates sync to server
- Topbar shows "Offline — editing locally" while disconnected but IndexedDB is ready
Server A (clients 1,2) Redis Server B (clients 3,4)
───────────────────── ───── ─────────────────────
Client 1 types
→ setupWSConnection broadcasts to clients on A
→ ydoc.on('update') fires
→ publishToRedis('ydoc:DOCID', update) → redisSub receives 'ydoc:DOCID'
→ Y.applyUpdate(localYdoc)
→ setupWSConnection broadcasts to clients on B
syncdoc/
├── docker-compose.yml # app + postgres:16 + redis:7
├── Dockerfile
├── PROGRESS.md
│
├── server/
│ ├── .env.example # DATABASE_URL, REDIS_URL, PORT, CLIENT_ORIGIN
│ └── src/
│ ├── index.js # Express REST API (all async)
│ ├── collaboration.js # y-websocket + Redis pub/sub
│ ├── db.js # PostgreSQL via pg pool
│ └── utils.js # generateDocId (4-letter)
│
└── client/
└── src/
├── hooks/useYjsEditor.js # + IndexeddbPersistence, offlineReady state
└── components/
└── Topbar.jsx # shows "Offline — editing locally" status
# Start Postgres and Redis only
docker-compose up postgres redis -d
# Install deps and run servers
npm install
cp server/.env.example server/.env
# Edit server/.env — set DATABASE_URL and REDIS_URL
npm run devcd syncdoc
npm install
cp server/.env.example server/.env
# DATABASE_URL=postgresql://syncdoc:syncdoc@localhost:5432/syncdoc
# REDIS_URL=redis://localhost:6379
npm run devLeave REDIS_URL empty in .env. The server runs fine — just no cross-instance sync.
docker-compose up -d
# App: http://localhost:3001
# Postgres data: postgres_data volume
# Redis data: redis_data volumedocker-compose up -d --scale app=3
# Add a load balancer (nginx/caddy) in front with sticky sessions (or just round-robin — CRDT handles it)| Variable | Default | Required |
|---|---|---|
PORT |
3001 |
No |
NODE_ENV |
development |
No |
DATABASE_URL |
— | Yes |
REDIS_URL |
— (single-server mode if unset) | No |
CLIENT_ORIGIN |
http://localhost:5173 |
Prod: Yes |
- Auth — OAuth / magic-link
- Access permissions — read-only share links
- Comments — threaded inline comments
- Document list — recently visited (localStorage)
- Rate limiting middleware (express-rate-limit)
- Prometheus metrics endpoint