Server API Extension

The Server API extension owns the HTTP server. It exposes the REST routes for every document (data, styles, comments, headers, media and the other worksheet modules), history snapshots and image storage, and the registration points other extensions use to add routes.

Documentation

Install

npm install @jspreadsheet/server-api

Settings

Configure the extension by calling it as a function before the server starts:

Property Description
s3: object S3 credentials (key, secret, bucket, region, url). Enables the history (snapshot) routes, the image routes and api.setImage. Without it, those routes return S3 not defined.

Routes

The extension binds an HTTP server under /api. Requests carry the token in an Authorization: Bearer header and pass through your beforeConnect/beforeLoad/beforeChange hooks.

Route Description
POST /api/create Create a new document (guid, config in the body).
GET /api/<guid> Full document configuration.
DELETE /api/<guid> Destroy the document.
GET /api/<guid>/<worksheetIndex> One worksheet's configuration.
/api/<guid>/[<worksheetIndex>/]<module> Module routes (data, value, comments, style, header, media, ...). When the worksheet index is omitted, worksheet 0 is used. See the REST API reference.
GET/POST/DELETE /api/<guid>/history[/<version>] List, create, restore and delete snapshots (S3 required, owner only).
GET /api/<guid>/images/<filename> Serve an image stored through api.setImage (S3 required).

Extension registration points

Other extensions add routes through three static methods, normally from their license hook (see Extensions):

Method Description
api.setAction(fn) Registers global actions: fn(actions) adds handlers served at /api/<name>.
api.setModule(fn) Registers document routes: fn(routers) adds handlers served at /api/<guid>/[<worksheetIndex>/]<name>, executed with the document loaded.
api.setImage(guid, data) Stores an image in the configured S3 bucket and returns its URL. Used, for example, to offload base64 images from setMedia changes.

Usage Example

S3 storage plus a MongoDB adapter wired through the server options:

const server = require('@jspreadsheet/server');
const adapter = require('@jspreadsheet/server-mongodb');
const api = require('@jspreadsheet/server-api');

// License
const license = {
    clientId: '356a192b7913b04c54574d18c28d46e6395428ab',
    licenseKey: 'MmIyMDhmYmY4NGI1ZDY1ODAwNThjMGZkOTVkNjg2MmQ1NzZmYTFhOTBmZWI3N2M3ZmQ1N2Q3YjMwNDNhMjRhYmViYmRkNGVjZjZlMmNkNDVhODJhYzg1ZmRiY2E3OTJhYjA1ODQzNTliZGZiMmYwNWM4YmRmMjAyZmUwODA1NmEsZXlKamJHbGxiblJKWkNJNklqTTFObUV4T1RKaU56a3hNMkl3TkdNMU5EVTNOR1F4T0dNeU9HUTBObVUyTXprMU5ESTRZV0lpTENKdVlXMWxJam9pU25Od2NtVmhaSE5vWldWMElpd2laR0YwWlNJNk1UYzBNak0wTWpRd01Dd2laRzl0WVdsdUlqcGJJbXB6YUdWc2JDNXVaWFFpTENKamMySXVZWEJ3SWl3aWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0luVmxMbU52YlM1aWNpSXNJbU5rY0c0dWFXOGlMQ0pwYm5SeVlYTm9aV1YwY3k1amIyMGlMQ0p6Wm1OdlpHVmliM1F1WTI5dElpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5KbGJtUmxjaUlzSW5CaGNuTmxjaUlzSW1sdGNHOXlkR1Z5SWl3aWRtRnNhV1JoZEdsdmJuTWlMQ0pqYjIxdFpXNTBjeUlzSW5ObFlYSmphQ0lzSW1Ob1lYSjBjeUlzSW5CeWFXNTBJaXdpWW1GeUlpd2ljMmhsWlhSeklpd2lZMnh2ZFdRaUxDSnRZWE5ySWl3aWMyaGxaWFJ6SWl3aWMyVnlkbVZ5SWl3aWFXNTBjbUZ6YUdWbGRITWlYWDA9'
}

// Connect API to S3 for history and image upload
api({
    s3: {
        key: "",
        secret: "",
        bucket: "",
        region: "",
        url: "",
    },
});

server({
    port: 3000,
    load: async function(guid, auth, cachedConfiguration) {
        return await adapter.load(guid, auth, cachedConfiguration);
    },
    change: async function(guid, changes, auth, onerror) {
        return await adapter.change(guid, changes, auth, onerror);
    },
    create: async function(guid, config, auth) {
        return await adapter.create(guid, config, auth);
    },
    destroy: async function(guid, auth) {
        return await adapter.destroy(guid, auth);
    },
    replace: async function(guid, config, auth) {
        return await adapter.replace(guid, config, auth);
    },
    error: function(e) {
        console.error('Error', e)
    },
    license: license,
    extensions: { api },
});

Client API

Any HTTP client can call the REST routes. Write bodies are form-encoded; see HTTP conventions. No additional package is required: the example uses the standard fetch API available in browsers and Node.js.

Example: Updating Comments

POST /api/<guid>/<worksheetIndex>/comments sets comments. Body keys are cell names: a string value creates a simple note, and bracket-notation arrays create threaded comments (each entry requires comments and date, with optional name, user_id and edited):

// API Server
const baseUrl = "http://localhost:8009/api";
// Your authentication token
const token = "eyJhbGciOiJIUzUxMiIsInR5cCJ9.eyJkb21haW4iOiJsb2NhbGhvc3Q6ODAPQSJ9.Xr2Ir2-zEc_tqV5y6i";

// Spreadsheet Guid
const guid = '79b45919-c751-4e2b-a49a-6c1286e2fc03';

// New comments, keyed by cell name
const body = new URLSearchParams();
body.append('A1', 'first comment');
body.append('B3[0][comments]', 'Something');
body.append('B3[0][date]', new Date().toISOString());
body.append('B3[0][name]', 'Random name');

// Set new comments on worksheet 0
fetch(`${baseUrl}/${guid}/0/comments`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
  },
  body,
})
  .then((response) => response.json())
  .then((result) => {
    // { message: 'Done', rev: <n> }
    console.log(result);
  })
  .catch((err) => {
    console.log(err);
  });

Additional Resources