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
- Node.js 18+ and npm
- A database for persistence: MongoDB, PostgreSQL or Redis if you use a ready-made adapter, or anything else with your own adapter
- Nginx (or any reverse proxy) for TLS termination and WebSocket proxying
- AWS S3 credentials: optional, for image uploads and snapshots
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.