This repository contains a minimal full-stack demo that shows how to implement near real-time collaboration with SurveyJS Creator using:
- Incremental saves via
saveSurveyFunc(saveNo, callback) - Server-side ordering protection with
saveNo - Server-Sent Events (SSE) broadcast so connected clients see updates quickly
- Polling fallback if live stream is interrupted
SurveyJS Creator does not include Google-Docs-style cursor collaboration out of the box, but this pattern enables practical multi-user synchronization of survey JSON.
- Server: Node.js, Express, SSE
- Client: Plain HTML + SurveyJS Creator (CDN scripts)
- Storage in demo: In-memory room state (for simplicity)
-
server.js
Express API + SSE stream endpoint + in-memory room/version state. -
public/index.html
SurveyJS Creator UI and client sync logic:- Loads initial survey JSON from server
- Sends incremental saves with
saveNo - Applies remote updates received over SSE
- Falls back to polling when stream disconnects
-
package.json
App metadata and start script.
The client uses the callback-style hook:
creator.saveSurveyFunc = (saveNo, callback) => { ... }
saveSurveyFunc is callback-based. The save operation can still be asynchronous (for example fetch(...).then(...).catch(...)), and callback(saveNo, true|false) is called when the operation completes.
On each change:
- Survey Creator provides a monotonically increasing
saveNo - Client posts
{ clientId, saveNo, surveyJson }toPOST /api/survey/:roomId/save - Server returns whether the change was stored
- Client calls
callback(saveNo, true|false)to acknowledge result
For each client, server tracks lastSaveNo.
Rule:
- Accept only
saveNo > lastSaveNo - Ignore duplicate/late updates (
saveNo <= lastSaveNo)
This prevents stale updates from overwriting newer state when requests arrive out of order.
When server accepts an update:
- It stores the latest survey JSON
- Increments room
version - Broadcasts update over SSE to other clients in the same room
Each receiving client applies the incoming JSON to Survey Creator.
If SSE is interrupted, the client starts polling:
GET /api/survey/:roomId?sinceVersion=<lastKnownVersion>- If no changes are available, server responds with
hasUpdate: false - If changed, server returns latest schema and version
npm install
npm startApp runs at:
http://localhost:3000
Optional env var for cross-origin local testing:
CORS_ORIGIN=http://localhost:3000,http://localhost:5173
Open two browser windows/tabs:
http://localhost:3000/?room=demo-room&client=alicehttp://localhost:3000/?room=demo-room&client=bob
Then:
- Edit survey structure/properties in one client
- Changes auto-save and broadcast
- Second client receives and applies updates quickly
- This demo is intentionally simple and uses in-memory storage.
- Restarting server resets all room data.
- No cursor-level collaboration/selection tracking is implemented.
- For production, add persistent storage, authentication, authorization, and conflict strategy (e.g., base version checks or merge rules).
- This demo keeps state in memory; serverless cold starts or scale-out instances can reset or isolate state.
- Frontend on Vercel
- Backend on a Node host (Render / Railway / Fly.io / VM)
Deploy this repo as a Node service and set:
- Start command:
npm start - Port: use platform-provided
PORT(already supported inserver.js) - Env var
CORS_ORIGINto your Vercel site URL (or comma-separated list)
Example:
CORS_ORIGIN=https://your-app.vercel.app
You can deploy the same repo, then open the app using query params to point to backend:
apiBase: backend HTTP URL
Example URL:
https://your-app.vercel.app/?room=demo-room&client=alice&apiBase=https://your-backend.onrender.com
Open a second client:
https://your-app.vercel.app/?room=demo-room&client=bob&apiBase=https://your-backend.onrender.com
- Edit survey on client A
- Confirm save status updates
- Confirm client B receives the change in near real time
If you cannot deploy backend hosting yet:
- Run backend locally:
npm start - Expose it with tunnel tooling (for example
cloudflared tunnel --url http://localhost:3000) - Use the tunnel URL in
apiBasewhen opening your Vercel frontend