Getting started

Overview

Jspreadsheet Server is a Node.js backend that keeps a live, server-side spreadsheet in sync with every connected user over WebSocket. Any change made by one user is applied on the server, where formulas recalculate once on the authoritative instance, and broadcast to everyone else. An optional REST API extension exposes the same documents to your backend systems.

What the server handles and what you implement

The responsibilities are divided as follows:

  • The server handles real-time synchronization, conflict resolution, the spreadsheet engine and the REST API.
  • You implement two things: authentication (three hooks that authorize each request) and persistence (where documents are stored, with ready-made adapters for MongoDB, PostgreSQL and Redis).

Install

npm install @jspreadsheet/server

A minimal server

Example with in-memory persistence

The following server uses in-memory persistence and allows all requests. It is sufficient to test collaboration end to end:

const server = require('@jspreadsheet/server');

// In-memory persistence: documents are lost when the process exits.
// Replace with an adapter for production. See /docs/server/adapters
const documents = {};

server({
    port: 3000,
    config: {
        cors: { origin: '*' },
    },
    beforeConnect: async (auth) => true,
    beforeLoad: async (guid, auth) => true,
    beforeChange: async (guid, changes, auth) => true,
    // `cached` carries the live configuration when the document is
    // already in the server cache, so no storage read is needed.
    load: async (guid, auth, cached) => cached || documents[guid] || false,
    // `config` arrives parsed and normalized (worksheetId, namespace
    // and version are already filled in).
    create: async (guid, config, auth) => { documents[guid] = config; return { success: 1 }; },
    // `changes.instance` is the live server-side spreadsheet. Serializing
    // the whole document on every change is fine for a demo; real adapters
    // persist the operation instead.
    change: async (guid, changes, auth) => { documents[guid] = changes.instance.getConfig(); },
    replace: async (guid, config, auth) => { documents[guid] = config; },
    destroy: async (guid, auth) => { delete documents[guid]; },
    license: {
        clientId: process.env.JSS_CLIENT,
        licenseKey: process.env.JSS_LICENSE,
    },
    // Internal errors are silently dropped without this handler.
    error: (e) => console.error(e),
});

The license option is mandatory, and without it the server does not start. All other options are documented in the server options reference.

Run the server

node index.js

Connect a client

Client connection example

Point the front end at the server; everything in auth reaches your hooks on every request:

import jspreadsheet from 'jspreadsheet';
import client from '@jspreadsheet/client';

jspreadsheet.setExtensions({ client });

const remote = client.connect({
    url: 'http://localhost:3000',
    auth: { token },
});

connect opens the socket and returns { load, create, destroy, disconnect } for managing documents over that connection.

Test in two browser windows

Open the same document in two browser windows. Edits in one appear in the other in real time.

Where to go next

  1. Architecture: how the pieces fit, covering engine, cache, sync and REST.
  2. Adapters: replace the in-memory store with MongoDB, PostgreSQL, Redis or your own.
  3. Authentication: replace the three true returns above with real access control.
  4. Deployment: run it in production behind Nginx.