Data Grid Server

Jspreadsheet Server

The Jspreadsheet Server extension is a JavaScript plugin designed for real-time data sharing and persistence in Jspreadsheet. It is a WebSocket-based service hosted on your server that enables collaboration and interactivity within spreadsheets while maintaining 100% data control.

Highlights

  • Private Service: Host on your servers for total data privacy
  • Custom Authentication: Use your authentication methods
  • Custom Storage: Add your persistence mechanisms
  • Real-time Collaboration: Work together on spreadsheets in real time
  • Lightweight: Experience seamless use with intuitive controls
  • WebSockets: Ensure smooth communication with Jspreadsheet

Jspreadsheet Server enhances your applications with real-time collaboration and data persistence securely hosted on your servers.

Documentation

The Jspreadsheet Server maintains a remote spreadsheet configuration for sharing across different users.

Changelog

11.11.0

  • Added compression support;
  • Introduced before events for authentication and request transformations;
  • The auth property has been dropped; please use beforeConnect and beforeLoad;

11.9.0+

  • Changes to the method signature for auth, connect and disconnect. Now the argument is query defined on the frontend.

Installation

npm install @jspreadsheet/server 

Settings

Description
port: number
The service port the server will listen on. Default: 3000.
config: object
Defines extra headers, such as CORS configurations.
license: object
License information required for server deployment.
extensions: object
Allows loading additional features or customizations via extensions.
beforeConnect: async function(auth) => boolean
Intercepts and validates user connections before they are established.
beforeLoad: async function(guid, auth) => boolean
Intercepts and authorizes access before loading a spreadsheet by its GUID.
beforeChange: async function(guid, changes, auth) => boolean
Intercepts and processes changes before they are applied to a spreadsheet.
load: async function(guid, auth, cache) => object | boolean
Loads the spreadsheet configuration by its GUID. Returns the configuration object or a boolean.
create: async function(string, config, auth) => object
Creates a new spreadsheet with the given configuration.
change: async function(string, changes, auth, onerror) => object
Updates a spreadsheet with the specified data and configuration.
replace: async function(string, config, auth) => boolean
Overwrites the spreadsheet’s settings, including the data.
destroy: async function(string, auth) => boolean
Deletes a spreadsheet using its GUID.
error: async function(e) => void
Handles and logs errors, providing custom error management.

Development considerations

Jspreadsheet Server is highly flexible and requires you to declare features such as authentication and persistence events. The following considerations apply to each available event:

Before Events

These events ensure users have appropriate access to spreadsheets.

Error

Enables developers to save errors to files for debugging purposes.

Limitations

Functions or references cannot be persisted from the client to the server. Therefore, you must manage your events on the front end.

Server Monitoring Employing tools like pm2 or supervisor is essential for server monitoring. These tools automate process management, ensuring your server remains operational and enhances deployment reliability.

Basic template

Create a Server With Persistence

This template demonstrates setting up a Jspreadsheet server for collaborative editing and persistence, including server initialization, user authentication, and spreadsheet event handling.

const server = require('@jspreadsheet/server'); // Jspreadsheet license: Both available on your profile const license = { clientId: 'your-client-id', licenseKey: 'your-certificate-license' } // Create a new server server({ port: 3000, error: async function(e) { // Save the error in a file }, load: async function(guid, auth) { // Load an existing spreadsheet based on the GUID identifier }, create: async function(guid, config, auth) { // Create a new spreadsheet }, destroy: async function(guid, auth) { // Destroy an existing spreadsheet }, change: async function(guid, changes, auth, onerror) { // Update an existing spreadsheet }, replace: async function(guid, config, auth) { // Overwrite the existing spreadsheet }, license: license, }); 

Example

Saving the data with Redis

Server Side

This example demonstrates a basic data persistence implementation using Redis on the server side without access restrictions.

const server = require('@jspreadsheet/server'); const { createClient } = require("redis"); const client = createClient({ socket: { host: 'redis', port: 6379 }, }); // Connect to the server client.connect(); // Jspreadsheet license const license = { clientId: '356a192b7913b04c54574d18c28d46e6395428ab', licenseKey: 'MmIyMDhmYmY4NGI1ZDY1ODAwNThjMGZkOTVkNjg2MmQ1NzZmYTFhOTBmZWI3N2M3ZmQ1N2Q3YjMwNDNhMjRhYmViYmRkNGVjZjZlMmNkNDVhODJhYzg1ZmRiY2E3OTJhYjA1ODQzNTliZGZiMmYwNWM4YmRmMjAyZmUwODA1NmEsZXlKamJHbGxiblJKWkNJNklqTTFObUV4T1RKaU56a3hNMkl3TkdNMU5EVTNOR1F4T0dNeU9HUTBObVUyTXprMU5ESTRZV0lpTENKdVlXMWxJam9pU25Od2NtVmhaSE5vWldWMElpd2laR0YwWlNJNk1UYzBNak0wTWpRd01Dd2laRzl0WVdsdUlqcGJJbXB6YUdWc2JDNXVaWFFpTENKamMySXVZWEJ3SWl3aWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0luVmxMbU52YlM1aWNpSXNJbU5rY0c0dWFXOGlMQ0pwYm5SeVlYTm9aV1YwY3k1amIyMGlMQ0p6Wm1OdlpHVmliM1F1WTI5dElpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5KbGJtUmxjaUlzSW5CaGNuTmxjaUlzSW1sdGNHOXlkR1Z5SWl3aWRtRnNhV1JoZEdsdmJuTWlMQ0pqYjIxdFpXNTBjeUlzSW5ObFlYSmphQ0lzSW1Ob1lYSjBjeUlzSW5CeWFXNTBJaXdpWW1GeUlpd2ljMmhsWlhSeklpd2lZMnh2ZFdRaUxDSnRZWE5ySWl3aWMyaGxaWFJ6SWl3aWMyVnlkbVZ5SWl3aWFXNTBjbUZ6YUdWbGRITWlYWDA9' } server({ port: 3000, // Socket.io server configuration config: { cors: { origin: "*" }, }, error: async function(e) { console.log(e); // Kill the thread process.exit(1); }, beforeLoad: async function(guid, auth) { return true; }, load: async function(guid) { return await client.get(guid); }, create: async function(guid, config) { const result = await client.exists(guid); if (result) { // A spreadsheet already exists return false; } else { // Create a new spreadsheet await client.set(guid, config); return true; } }, destroy: async function(guid) { return await client.del(guid) .then(() => true) .catch(() => false); }, change: async function(guid, changes) { // Get the configuration from the cache let config = changes.instance.getConfig(); // Save that on the redis await client.set(guid, JSON.stringify(config)); }, license: license, }); 

Client

Connect to your server to create and open an existing remote spreadsheet using the spreadsheet GUID identifier.

<html> <script src="https://cdn.jsdelivr.net/npm/jspreadsheet@11/dist/index.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet@11/dist/jspreadsheet.min.css" type="text/css" /> <script src="https://cdn.jsdelivr.net/npm/jsuites/dist/jsuites.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsuites/dist/jsuites.min.css" type="text/css" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" /> <script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script> <script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@jspreadsheet/formula-pro/dist/index.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@jspreadsheet/client/dist/index.min.js"></script> <div id="spreadsheet"></div> <script> // You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox. // The license is valid for one day, after which the spreadsheet will become read-only. // For a longer trial period, you can create a free account and generate a demo license with an extended expiration date. jspreadsheet.setLicense('OGRlM2M2NDdkYTFiMWJkYzc3NWE0ZTEwNWE4ZmU4ZTMzODQ0ZGZiNjhkMDJmODM3OTIxMWM5NDlmYWY0MjhiNzNlZmVjNDZiMmI5MzU3MmQ3OTI4NWVhMTA2NGRiOGY1MTUxYjBmOWY0MWE2ODc4N2I3MzI4ZjI1NWFhNzc0YmUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpjME1UUXlNelk0TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElpd2ljR2wyYjNRaVhTd2laR1Z0YnlJNmRISjFaWDA9'); jspreadsheet.setExtensions({ formula, client }); // Connect let remote = client.connect({ // Point this to your own domain server url: 'https://jspreadsheet.com', // Internal socket path path: 's/', // Can be used to send extra information to the server to validate this user connection token: 'jwt-token' }); // Create in case does not exist. worksheetName is mandatory remote.create('53aa4c90-791d-4a65-84a6-8ac25d6b1101', { tabs: true, toolbar: true, tableOverflow: true, tableWidth: '600px', worksheets: [{ minDimensions: [4,6], // Worksheet name is mandatory worksheetName: 'Sheet1', }] }); // Connect to a spreadsheet jspreadsheet(document.getElementById('spreadsheet'), { guid: '53aa4c90-791d-4a65-84a6-8ac25d6b1101' }); </script> 
import React, {useRef} from 'react'; import { Spreadsheet, jspreadsheet } from '@jspreadsheet/react'; import formula from '@jspreadsheet/formula-pro'; import client from '@jspreadsheet/client'; import "jsuites/dist/jsuites.css"; import "jspreadsheet/dist/jspreadsheet.css"; const license = { clientId: '356a192b7913b04c54574d18c28d46e6395428ab', licenseKey: 'MmIyMDhmYmY4NGI1ZDY1ODAwNThjMGZkOTVkNjg2MmQ1NzZmYTFhOTBmZWI3N2M3ZmQ1N2Q3YjMwNDNhMjRhYmViYmRkNGVjZjZlMmNkNDVhODJhYzg1ZmRiY2E3OTJhYjA1ODQzNTliZGZiMmYwNWM4YmRmMjAyZmUwODA1NmEsZXlKamJHbGxiblJKWkNJNklqTTFObUV4T1RKaU56a3hNMkl3TkdNMU5EVTNOR1F4T0dNeU9HUTBObVUyTXprMU5ESTRZV0lpTENKdVlXMWxJam9pU25Od2NtVmhaSE5vWldWMElpd2laR0YwWlNJNk1UYzBNak0wTWpRd01Dd2laRzl0WVdsdUlqcGJJbXB6YUdWc2JDNXVaWFFpTENKamMySXVZWEJ3SWl3aWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0luVmxMbU52YlM1aWNpSXNJbU5rY0c0dWFXOGlMQ0pwYm5SeVlYTm9aV1YwY3k1amIyMGlMQ0p6Wm1OdlpHVmliM1F1WTI5dElpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5KbGJtUmxjaUlzSW5CaGNuTmxjaUlzSW1sdGNHOXlkR1Z5SWl3aWRtRnNhV1JoZEdsdmJuTWlMQ0pqYjIxdFpXNTBjeUlzSW5ObFlYSmphQ0lzSW1Ob1lYSjBjeUlzSW5CeWFXNTBJaXdpWW1GeUlpd2ljMmhsWlhSeklpd2lZMnh2ZFdRaUxDSnRZWE5ySWl3aWMyaGxaWFJ6SWl3aWMyVnlkbVZ5SWl3aWFXNTBjbUZ6YUdWbGRITWlYWDA9' } jspreadsheet.setLicense(license); jspreadsheet.setExtensions({ formula, client }); const guid = '53aa4c90-791d-4a65-84a6-8ac25d6b1105' // Connect to the server let remote = client.connect({ // Point this to your own domain server url: 'https://yourserver.com', // Internal socket path path: 'server/socket.io/', // Can be used to send extra information to the server to validate this user connection token: 'jwt-token' }); // Create only once. Do nothing if already exists remote.create(guid, { tabs: true, toolbar: true, worksheets: [{ minDimensions: [4,6], // Worksheet name is mandatory worksheetName: 'Sheet1', }] }).then((result) => { console.log(result); }); export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <Spreadsheet ref={spreadsheet} guid={guid} /> ); } 
<template> <Spreadsheet ref="spreadsheet" :guid="guid"/> </template> <script> import { Spreadsheet, jspreadsheet } from "@jspreadsheet/vue"; import formula from "@jspreadsheet/formula-pro"; import client from "@jspreadsheet/client"; import "jsuites/dist/jsuites.css"; import "jspreadsheet/dist/jspreadsheet.css"; const license = { clientId: '356a192b7913b04c54574d18c28d46e6395428ab', licenseKey: 'MmIyMDhmYmY4NGI1ZDY1ODAwNThjMGZkOTVkNjg2MmQ1NzZmYTFhOTBmZWI3N2M3ZmQ1N2Q3YjMwNDNhMjRhYmViYmRkNGVjZjZlMmNkNDVhODJhYzg1ZmRiY2E3OTJhYjA1ODQzNTliZGZiMmYwNWM4YmRmMjAyZmUwODA1NmEsZXlKamJHbGxiblJKWkNJNklqTTFObUV4T1RKaU56a3hNMkl3TkdNMU5EVTNOR1F4T0dNeU9HUTBObVUyTXprMU5ESTRZV0lpTENKdVlXMWxJam9pU25Od2NtVmhaSE5vWldWMElpd2laR0YwWlNJNk1UYzBNak0wTWpRd01Dd2laRzl0WVdsdUlqcGJJbXB6YUdWc2JDNXVaWFFpTENKamMySXVZWEJ3SWl3aWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0luVmxMbU52YlM1aWNpSXNJbU5rY0c0dWFXOGlMQ0pwYm5SeVlYTm9aV1YwY3k1amIyMGlMQ0p6Wm1OdlpHVmliM1F1WTI5dElpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5KbGJtUmxjaUlzSW5CaGNuTmxjaUlzSW1sdGNHOXlkR1Z5SWl3aWRtRnNhV1JoZEdsdmJuTWlMQ0pqYjIxdFpXNTBjeUlzSW5ObFlYSmphQ0lzSW1Ob1lYSjBjeUlzSW5CeWFXNTBJaXdpWW1GeUlpd2ljMmhsWlhSeklpd2lZMnh2ZFdRaUxDSnRZWE5ySWl3aWMyaGxaWFJ6SWl3aWMyVnlkbVZ5SWl3aWFXNTBjbUZ6YUdWbGRITWlYWDA9' } jspreadsheet.setLicense(license); jspreadsheet.setExtensions({ formula, client }); const guid = '53aa4c90-791d-4a65-84a6-8ac25d6b1105' // Connect to the server let remote = client.connect({ // Point this to your own domain server url: 'https://yourserver.com', // Can be used to send extra information to the server to validate this user connection token: 'jwt-token' }); // Create only once. Do nothing if already exists remote.create(guid, { tabs: true, toolbar: true, worksheets: [{ minDimensions: [4,6], // Worksheet name is mandatory worksheetName: 'Sheet1', }] }) .then((result) => { console.log(result) }); export default { components: { Spreadsheet, }, data() { return { guid }; } } </script> 
import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet"; import formula from "@jspreadsheet/formula-pro"; import client from "@jspreadsheet/client"; import "jsuites/dist/jsuites.css"; import "jspreadsheet/dist/jspreadsheet.css"; const license = { clientId: '356a192b7913b04c54574d18c28d46e6395428ab', licenseKey: 'MmIyMDhmYmY4NGI1ZDY1ODAwNThjMGZkOTVkNjg2MmQ1NzZmYTFhOTBmZWI3N2M3ZmQ1N2Q3YjMwNDNhMjRhYmViYmRkNGVjZjZlMmNkNDVhODJhYzg1ZmRiY2E3OTJhYjA1ODQzNTliZGZiMmYwNWM4YmRmMjAyZmUwODA1NmEsZXlKamJHbGxiblJKWkNJNklqTTFObUV4T1RKaU56a3hNMkl3TkdNMU5EVTNOR1F4T0dNeU9HUTBObVUyTXprMU5ESTRZV0lpTENKdVlXMWxJam9pU25Od2NtVmhaSE5vWldWMElpd2laR0YwWlNJNk1UYzBNak0wTWpRd01Dd2laRzl0WVdsdUlqcGJJbXB6YUdWc2JDNXVaWFFpTENKamMySXVZWEJ3SWl3aWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0luVmxMbU52YlM1aWNpSXNJbU5rY0c0dWFXOGlMQ0pwYm5SeVlYTm9aV1YwY3k1amIyMGlMQ0p6Wm1OdlpHVmliM1F1WTI5dElpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5KbGJtUmxjaUlzSW5CaGNuTmxjaUlzSW1sdGNHOXlkR1Z5SWl3aWRtRnNhV1JoZEdsdmJuTWlMQ0pqYjIxdFpXNTBjeUlzSW5ObFlYSmphQ0lzSW1Ob1lYSjBjeUlzSW5CeWFXNTBJaXdpWW1GeUlpd2ljMmhsWlhSeklpd2lZMnh2ZFdRaUxDSnRZWE5ySWl3aWMyaGxaWFJ6SWl3aWMyVnlkbVZ5SWl3aWFXNTBjbUZ6YUdWbGRITWlYWDA9' } jspreadsheet.setLicense(license); // Define the data grid extensions jspreadsheet.setExtensions({ client, formula }); // Connect let remote = client.connect({ // Point this to your own domain server url: 'https://yourserver.com', // Can be used to send extra information to the server to validate this user connection token: 'jwt-token' }); // Create in case does not exist remote.create(guid, { tabs: true, toolbar: true, worksheets: [{ minDimensions: [4,6], // Worksheet name is mandatory worksheetName: 'Sheet1', }] }) .then((result) => { console.log(result) }); @Component({ selector: "app-root", template: `<div #spreadsheet></div>` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { guid: '53aa4c90-791d-4a65-84a6-8ac25d6b1105' }); } } 

More resources

Real-time Spreadsheets with React Typescript

Use this React TypeScript project as a template to set up a real-time collaborative spreadsheet server within minutes.

Jspreadsheet Server Nginx Setup

You can quickly enable your server using Nginx by following this tutorial: