Deployment

Jspreadsheet Server is a plain Node.js application: one process that serves the REST API and the real-time WebSocket channel on a single port. Deploying it means running that process under a supervisor and putting a reverse proxy in front of it. This page walks through a production setup on a Linux host.

Requirements

Install

Create the application and install the server plus the adapter for your database:

mkdir /var/jspreadsheet && cd /var/jspreadsheet
npm init -y
npm install @jspreadsheet/server @jspreadsheet/server-mongodb @jspreadsheet/server-api dotenv
# or: @jspreadsheet/server-pgsql, @jspreadsheet/server-redis

The entry point

Your entry point wires the adapter, your authorization hooks and the extensions into the server. The full wiring is covered in Adapters and Authentication. Note that the REST API is provided by the api extension: without it in extensions, the server only speaks WebSocket and no /api route exists.

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

require('dotenv').config();

server({
    port: 3000,
    config: {
        cors: { origin: process.env.CORS_ORIGIN },
    },
    beforeConnect: async (auth) => { /* your gate */ return true; },
    beforeLoad: async (guid, auth) => { /* your gate */ return true; },
    beforeChange: async (guid, changes, auth) => { /* your gate */ return true; },
    load: (guid, auth, cached) => adapter.load(guid, auth, cached),
    change: (guid, changes, auth, onerror) => adapter.change(guid, changes, auth, onerror),
    create: (guid, config, auth) => adapter.create(guid, config, auth),
    replace: (guid, config, auth) => adapter.replace(guid, config, auth),
    destroy: (guid, auth) => adapter.destroy(guid, auth),
    list: (auth) => adapter.list(auth),
    error: (e) => console.error(e),
    extensions: { api },
    license: {
        clientId: process.env.JSS_CLIENT,
        licenseKey: process.env.JSS_LICENSE,
    },
});

Environment

Keep credentials in a .env file (or your secret manager), never in code:

# License
JSS_CLIENT=""
JSS_LICENSE=""

# Database: whichever your adapter uses
MONGO_URL="mongodb://localhost:27017/jspreadsheet"
# POSTGRES_URL="postgres://user:pass@localhost:5432/jspreadsheet"

# JWT verification secret (if you issue your own tokens)
JWT_SECRET=""

# CORS
CORS_ORIGIN="https://app.example.com"

# AWS S3: optional, for images and snapshot backups
AWS_S3_KEY=""
AWS_S3_SECRET=""
AWS_BUCKET=""
AWS_S3_REGION=""
AWS_S3_URL=""

There is no schema to load by hand: the official adapters create and manage their own tables or collections on first use. See the PostgreSQL adapter for what gets created.

Run under a supervisor

Running with pm2

Any process manager works. With pm2:

npm install -g pm2
pm2 start index.js --name jspreadsheet
pm2 save && pm2 startup   # restart on reboot

Running with systemd

Or as a systemd unit:

# /etc/systemd/system/jspreadsheet.service
[Unit]
Description=Jspreadsheet Server
After=network.target

[Service]
WorkingDirectory=/var/jspreadsheet
ExecStart=/usr/bin/node index.js
Restart=always
User=www-data
EnvironmentFile=/var/jspreadsheet/.env

[Install]
WantedBy=multi-user.target
systemctl enable --now jspreadsheet

Multiple instances

Each document lives in the memory of exactly one server process, so you cannot round-robin traffic across instances. Route by document guid instead. The model is explained in Scaling.

Nginx reverse proxy

Terminate TLS at Nginx and proxy both HTTP and WebSocket traffic to the Node process. The Upgrade/Connection headers are required for the Socket.IO handshake to work through the proxy:

server {
    listen 443 ssl;
    server_name sheets.example.com;

    ssl_certificate     /etc/letsencrypt/live/sheets.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sheets.example.com/privkey.pem;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}
nginx -t && systemctl reload nginx

Authentication tokens travel in the WebSocket handshake and in Bearer headers, so the proxy must serve HTTPS/WSS. See the security checklist. More proxy variants (path-prefix mounting, rate limiting) are on the Nginx page.

Verify

# The API answers: fetch a document's configuration
curl -s https://sheets.example.com/api/<guid> -H "Authorization: Bearer <token>"

(GET /api/list also works as a check if the intrasheets extension is registered, since it is the extension that provides that route.)

Then point the client at the deployment:

const remote = client.connect({
    url: 'https://sheets.example.com',
    auth: { token },
});

What's Next?

  • Scaling: memory sizing, guid routing, multi-instance topologies.
  • Adapters: wire the persistence layer.
  • Login & security: the production security checklist.