Deployment
Jspreadsheet Server is a plain Node.js application: one process serves the REST API and the real-time WebSocket channel on a single port. In production, run it under a supervisor behind a reverse proxy. The examples below target 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
Install the server and 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
The entry point wires the adapter, authorization hooks and extensions into the server (details in Adapters and Authentication). 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, for your own adapter (the ready-made MongoDB adapter always connects to mongodb://mongodb)
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=""
No manual schema setup is needed: the official adapters create 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. See 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:
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, but only if the intrasheets extension (which provides that route) is registered.
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.