XRS Reactive Server
xrs is a JavaScript library designed for building reactive servers and clients, currently at version 1.2.2. It facilitates full-duplex communication by treating both client requests and server responses as streams, promises, or plain values. The server component integrates the Express framework for routing and μWS (uws) for high-performance WebSocket handling, enabling efficient management of both HTTP and WebSocket connections. A key differentiator is its emphasis on stream-based interactions, allowing complex real-time data flows and supporting features like binary uploads with progress event tracking. The client-side library is designed to be lightweight, bundling at approximately 3 KB. While specific release cadence information is not provided, the current version suggests it is either actively maintained or stable. It's suitable for applications requiring low-latency, real-time communication with built-in stream processing capabilities.
Common errors
-
Error: listen EADDRINUSE :::<port_number>
cause The specified server port is already in use by another process on the system.fixChoose a different port for your XRS server, or identify and terminate the process currently using the conflicting port. -
TypeError: xrs is not a function
cause This typically occurs when attempting to call `xrs` after an incorrect CommonJS `require` (e.g., `const { xrs } = require('xrs')`) or when trying to use the client-side `xrs` return value as a server constructor.fixEnsure correct CommonJS/ESM import: `const xrs = require('xrs')` or `import xrs from 'xrs'`. For server creation, use `const server = xrs(processor)` or `const server = xrs({ port, processor })`. For client, use `const send = xrs(url)`. -
UnhandledPromiseRejectionWarning: A promise was rejected with a reason that was not handled.
cause A Promise returned by the server's processor function or emitted by a client-side stream was rejected without an attached `.catch()` handler or an `.on('error', handler)` listener.fixOn the server, ensure all Promises returned from the processor function are resolved or explicitly caught. On the client, always attach an `.on('error', handler)` to the stream returned by `send` to handle potential errors from the server.
Warnings
- gotcha If the `port` option is omitted when creating an XRS server, it will default to a random available HTTP port. This can make client connection unpredictable.
- gotcha By default, the XRS server runs over HTTP. To enable HTTPS (secure communication), explicit SSL certificates must be provided in the server options.
- gotcha Forgetting to attach `.on('error', handler)` listeners to streams (both client-side responses and server-side processor outputs) can lead to unhandled promise rejections or silent failures, especially with asynchronous operations.
Install
-
npm install xrs -
yarn add xrs -
pnpm add xrs
Imports
- xrs
import { xrs } from 'xrs'; // xrs is the default export const server = require('xrs')().listen(3000); // Incorrect CJS usage or server startupimport xrs from 'xrs'; const server = xrs({ port: 3000, processor: req => 'ack' }); - xrs
import { send } from 'xrs'; // `send` is returned by xrs(), not a named export const send = xrs().connect('ws://localhost:3000'); // Incorrect method chainingimport xrs from 'xrs'; const send = xrs('ws://localhost:3000'); - xrsServer.http
import { http } from 'xrs'; // Not directly exported xrsServer.listen(3000); // Incorrectly assuming xrsServer has a direct .listen methodimport xrs from 'xrs'; const xrsServer = xrs({ port: 3000, processor: req => 'ack' }); xrsServer.http.on('listening', () => console.log('Server is live'));
Quickstart
import xrs from 'xrs';
import http from 'http'; // For server closing
async function runXRSExample() {
const serverPort = 4000; // Choose a specific port
// 1. Start the XRS Server
// The 'xrs' function can take a processor or an options object.
// We pass options to specify the port.
const xrsServer = xrs({
port: serverPort,
processor: (req) => {
console.log(`[Server] Received data from client (id: ${req.id}):`, req.data.toString());
if (req.data === 'trigger-error') {
throw new Error('Simulated server error');
}
return `Echo: ${req.data.toString()}`; // Send back a response
}
});
// Access the underlying HTTP server instance to confirm it's listening
// and for clean shutdown.
await new Promise<void>(resolve => {
xrsServer.http.on('listening', () => {
console.log(`[Server] XRS server running on ws://localhost:${serverPort}`);
resolve();
});
});
// 2. Initialize the XRS Client
// Pass the server URL to connect.
const send = xrs(`ws://localhost:${serverPort}`);
// 3. Client sends a simple value
console.log('[Client] Sending "Hello Reactive Server!"');
const clientResponseStream1 = await send('Hello Reactive Server!');
clientResponseStream1.on('data', (data) => {
console.log('[Client] Received response:', data);
});
clientResponseStream1.on('error', (err) => {
console.error('[Client] Error on stream 1:', err);
});
// 4. Client sends a binary stream with progress events
const binaryBuffer = Buffer.from('This is a test binary payload for upload.');
console.log('[Client] Sending binary data...');
const results: any[] = [];
const clientResponseStream2 = await send(binaryBuffer, { metadata: 'binary-upload' });
clientResponseStream2
.on('sent', d => results.push({ type: 'sent', value: d }))
.on('progress', d => results.push({ type: 'progress', value: d }))
.on('data', d => results.push({ type: 'complete', value: d }));
clientResponseStream2.on('end', () => {
console.log('[Client] Binary upload events:', results);
});
// 5. Client sends a promise that resolves
console.log('[Client] Sending a Promise that resolves...');
const promiseResponseStream = await send(Promise.resolve('Data from Promise'));
promiseResponseStream.on('data', (data) => {
console.log('[Client] Received response for Promise:', data);
});
// Wait a bit, then close the server
setTimeout(() => {
console.log('[Server] Shutting down XRS server.');
xrsServer.http.close(() => {
console.log('[Server] XRS server closed.');
});
}, 3000);
}
runXRSExample().catch(console.error);