Server-Sent Events
Harper supports Server-Sent Events (SSE), a simple and efficient mechanism for browser-based applications to receive real-time updates from the server over a standard HTTP connection. SSE is a one-directional transport — the server pushes events to the client, and the client has no way to send messages back on the same connection.
Connecting
SSE connections are made by targeting a resource URL. By default, connecting to a resource path subscribes to changes for that resource and streams events as they occur.
let eventSource = new EventSource('https://server/my-resource/341', {
withCredentials: true,
});
eventSource.onmessage = (event) => {
let data = JSON.parse(event.data);
};
The URL path maps to the resource in the same way as REST and WebSocket connections. Connecting to /my-resource/341 subscribes to updates for the record with id 341 in the my-resource table (or custom resource).
connect() Handler
SSE connections use the same connect() method as WebSockets on resource classes, with one key difference: since SSE is one-directional, connect() is called without an incomingMessages argument.
export class MyResource extends Resource {
async *connect() {
// yield messages to send to the client
while (true) {
await someCondition();
yield { event: 'update', data: { value: 42 } };
}
}
}
The default connect() behavior subscribes to the resource and streams changes automatically.
Reading the request in connect()
To read the incoming request (query parameters, headers, the path), define connect() as a static method. Its first argument is the RequestTarget, which exposes the query string via target.get():
export class Search extends Resource {
static connect(target) {
const query = target.get('q'); // GET /Search?q=harper
const apiKey = target.get('api_key');
return this.stream(query, apiKey); // return the generator that streams
}
static async *stream(query, apiKey) {
for (const hit of await runSearch(query)) yield { data: hit };
}
}
connect() itself is not the generator. It reads what it needs from target synchronously, then returns the async * generator that yields events. You can also return an inline generator: return (async function* () { … })().
Note: the
RequestTargetargument is only passed to astaticconnect(). An instance method's first argument is not the target, so defineconnect()(and any delegated generator) asstatic.
When to Use SSE vs WebSockets
| SSE | WebSockets | |
|---|---|---|
| Direction | Server → Client only | Bidirectional |
| Transport | Standard HTTP | HTTP upgrade |
| Browser support | Native EventSource API | Native WebSocket API |
| Use case | Live feeds, dashboards, notifications | Interactive real-time apps, MQTT |
SSE is simpler to implement and has built-in reconnection in browsers. For scenarios requiring bidirectional communication, use WebSockets.
See Also
- WebSockets — Bidirectional real-time connections
- MQTT Overview — Full MQTT pub/sub documentation
- REST Overview — HTTP methods and URL structure
- Resources — Custom resource API including
connect()