ExpreSSE is a set of middlewares - with a simple and elegant API - for working with Server-Sent Events (SSE) in Express. SSE is a simple unidirectional protocol that lets an HTTP server push messages to a client that uses window.EventSource
. It's HTTP long-polling, without polling!
From the MDN:
Traditionally, a web page has to send a request to the server to receive new data; that is, the page requests data from the server. With server-sent events, it's possible for a server to send new data to a web page at any time, by pushing messages to the web page.
- Installation & Usage
sse()
middleware — one to one (server to 1 client)sseHub()
middleware — one to many (server to n clients)- Notes:
About browser support, Using a serializer for messages'
data
field
📦 Installation & Usage
Requirements:
- Node.js 5+ because ExpreSSE is transpiled down to ES 6 ;
- Express 4
Install it via the npm registry:
yarn add @toverux/expresse
TypeScript users: the library as distributed on npm already contains type definitions for TypeScript.
sse()
middleware
Import the middleware
-
Using ES 2015 imports:
ISseResponse
is a TypeScript interface. Don't try to import it when using JavaScript.import { ISseResponse, sse } from '@toverux/expresse'; // named export { sse } is also exported as { default }: import sse from '@toverux/expresse';
-
Using CommonJS:
const { sse } = require('@toverux/expresse');
Available configuration options
interface ISseMiddlewareOptions {
/**
* Serializer function applied on all messages' data field (except when you direclty pass a Buffer).
* SSE comments are not serialized using this function.
*
* @default JSON.stringify
*/
serializer?: (value: any) => string|Buffer;
/**
* Determines the interval, in milliseconds, between keep-alive packets (neutral SSE comments).
*
* @default 5000
*/
keepAliveInterval?: number;
}
Usage example (remove ISseResponse
when not using TypeScript):
// somewhere in your module
router.get('/events', sse(/* options */), (req, res: ISseResponse) => {
let messageId = parseInt(req.header('Last-Event-ID'), 10) || 0;
someModule.on('someEvent', (event) => {
//=> Data messages (no event name, but defaults to 'message' in the browser).
res.sse.data(event);
//=> Named event + data (data is mandatory)
res.sse.event('someEvent', event);
//=> Comment, not interpreted by EventSource on the browser - useful for debugging/self-documenting purposes.
res.sse.comment('debug: someModule emitted someEvent!');
//=> In data() and event() you can also pass an ID - useful for replay with Last-Event-ID header.
res.sse.data(event, (messageId++).toString());
});
// (not recommended) to force the end of the connection, you can still use res.end()
// beware that the specification does not support server-side close, so this will result in an error in EventSource.
// prefer sending a normal event that asks the client to call EventSource#close() itself to gracefully terminate.
someModule.on('someFinishEvent', () => res.end());
});
sseHub()
middleware
This one is very useful for pushing the same messages to multiples users at a time, so they share the same "stream".
It is based on the sse()
middleware, meaning that you can still use res.sse.*
functions, their behavior don't change.
For broadcasting to the users that have subscribed to the stream (meaning that they've made the request to the endpoint), use the req.sse.broadcast.*
functions, that are exactly the same as their 1-to-1 variant.
Import the middleware
-
Using ES 2015 imports:
ISseHubResponse
is a TypeScript interface. Don't try to import it when using JavaScript.import { Hub, ISseHubResponse, sseHub } from '@toverux/expresse';
-
Using CommonJS:
const { Hub, sseHub } = require('@toverux/expresse');
Available configuration options
The options are the same from the sse()
middleware (see above), plus another, hub
:
interface ISseHubMiddlewareOptions extends ISseMiddlewareOptions {
/**
* You can pass a Hub instance for controlling the stream outside of the middleware.
* Otherwise, a Hub is automatically created.
*
* @default Hub
*/
hub: Hub;
}
First usage example - where the client has control on the hub (remove ISseHubResponse
when not using TypeScript):
// somewhere in your module
router.get('/events', sseHub(/* options */), (req, res: ISseHubResponse) => {
//=> The 1-to-1 functions are still there
res.sse.event('welcome', 'Welcome to you!');
//=> But we also get a `broadcast` property with the same functions inside.
// Everyone that have hit /events will get this message - including the sender!
res.sse.broadcast.event('newcomer', 'someone just hit the /events endpoint');
});
More common usage example - where the Hub is deported outside of the middleware:
const hub = new Hub();
someModule.on('someEvent', (event) => {
//=> All the functions you're now used to are still there, data(), event() and comment().
hub.event('someEvent', event);
});
router.get('/events', sseHub({ hub }), (req, res: ISseHubResponse) => {
//=> The 1-to-1 functions are still there
res.sse.event('welcome', 'Welcome! You\'ll now receive realtime events like everyone else');
});
💡 Notes
About browser support
The W3C standard client for Server-Sent events is EventSource. Unfortunately, it is not yet implemented in Internet Explorer or Microsoft Edge.
You may want to use a polyfill on the client side if your application targets those browsers.
Chrome | Edge | Firefox | Opera | Safari | |
---|---|---|---|---|---|
EventSource Support | 6 | No | 6 | Yes | 5 |
data
fields
Using a serializer for messages' When sending a message, the data
field is serialized using JSON.stringify
. You can override that default serializer to use your own format.
The serializer must be compatible with the signature (value: any) => string|Buffer;
.
For example, to format data using the toString()
format of the value, you can use the String()
constructor:
app.get('/events', sse({ serializer: String }), yourMiddleware);
// or, less optimized:
app.get('/events', sse({ serializer: data => data.toString() }), yourMiddleware);