Jspreadsheet and Express
Jspreadsheet Server can attach its real-time WebSocket traffic to an HTTP server you create, which allows an Express application and the spreadsheet server to share one process and one port. This page covers the setup, which routes each side owns, how middleware behaves, and the constraints to be aware of.
Full example
Complete Express and Jspreadsheet Server application
Create the Node HTTP server yourself, mount Express on it, and hand the same instance to Jspreadsheet Server through the server option:
const server = require('@jspreadsheet/server');
const adapter = require('@jspreadsheet/server-redis');
const express = require('express');
const http = require('http');
const app = express();
const httpServer = http.createServer(app);
app.get('/test', (req, res) => {
res.send('Hello world!');
});
// Load from ENV
require('dotenv').config();
// Jspreadsheet license
const license = {
clientId: process.env.JSS_CLIENT,
licenseKey: process.env.JSS_LICENSE
};
// Create Jspreadsheet Server
server({
config: {
cors: {
origin: "*"
},
},
port: 3000,
server: httpServer,
beforeConnect: async function(auth) {
// Allow everyone
return true;
},
beforeLoad: async function(guid, auth) {
// Allow everything
return true;
},
beforeChange: async function(guid, changes, auth) {
// Allow everything
return true;
},
load: async function(guid, auth, cachedConfiguration) {
// Load the spreadsheet when is not already in cache
if (! cachedConfiguration) {
// Load the spreadsheet from the adapter
cachedConfiguration = await adapter.load(guid, auth);
}
return 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);
},
error: function(e) {
console.error('Error', e)
},
license: license
});
How it works
Sharing one HTTP server and port
http.createServer(app) wraps the Express application in a plain Node HTTP server. When the server option is set, Jspreadsheet Server attaches its Socket.IO instance to that server and then starts it on port. Both traffic types answer on port 3000: /test is served by Express, while the WebSocket traffic is handled by Socket.IO on its own path.
Do not call listen yourself
Jspreadsheet Server calls listen(port) on the HTTP server you pass in. Pass a server that is not yet listening and do not call app.listen() or httpServer.listen(), because a second listen on the same server throws ERR_SERVER_ALREADY_LISTEN.
Route ownership
Socket.IO claims only its handshake path (/socket.io/ by default). Every other request on the shared server reaches the Express application unchanged, so your routes, static files and error handling work as in any Express app.
Middleware behavior
Express middleware runs only for HTTP requests that Express handles. WebSocket traffic does not pass through the Express middleware stack: body parsers, sessions and loggers do not see it, and they do not interfere with it. Authentication for spreadsheet traffic is handled by the server's hooks, not by Express middleware.
Access control hooks
The three before* hooks decide access. The example allows everyone; in a real application, validate the auth object (for example a token issued by your login flow) and return false to reject the request. An Express login route and the hooks can share the same token verification code, since the client sends the token in its auth option on every interaction.
Persistence with the Redis adapter
The load, change, create and destroy handlers delegate to the Redis adapter, so documents survive restarts. The cachedConfiguration argument lets the server skip a storage read when the document is already live in memory. Any adapter, or your own handlers, can be plugged in the same way.
Serving the front end from Express
The same Express application can serve the pages that host the spreadsheet. The front end connects with the client extension, pointing at the same origin:
// Express serves the application pages
app.use(express.static('public'));
// In the front end served from /public
import jspreadsheet from 'jspreadsheet';
import client from '@jspreadsheet/client';
jspreadsheet.setExtensions({ client });
const remote = client.connect({
url: window.location.origin,
auth: { token },
});
Limitations
The REST API extension binds its own HTTP server
The REST API is provided by the api extension, and that extension creates and binds its own HTTP server during license registration, replacing the server option. Enabling extensions: { api } and passing an Express httpServer in the same configuration therefore does not combine them: the REST server wins and the Express routes are not served.
To use Express routes and the REST API together, run them as separate services, the Express application on one port and the REST-enabled Jspreadsheet Server on another, then route paths at a reverse proxy such as Nginx. The WebSocket-only setup on this page (no api extension) is the configuration that shares a single port with Express.
Related pages
- Getting started: the minimal standalone server.
- Nginx: reverse proxy configuration, path prefixes and TLS.
- Authentication: the hooks that guard socket and REST access.
- Server options: the full
server()configuration reference.