Fastify Server-Sent Events (SSE) Plugin
fastify-sse-v2 is a Fastify plugin designed to streamline the implementation of Server-Sent Events (SSE) within a Fastify application. It augments the `FastifyReply` object with a `.sse()` method, enabling developers to send individual events or stream events from `AsyncIterable` or `EventEmitter` sources, thereby abstracting the underlying HTTP streaming complexities. The current stable version is 4.2.2, with releases occurring periodically, typically every few months, to address bug fixes and introduce minor features. A primary differentiator is its seamless integration with Fastify's reply object and robust support for modern async/await patterns in event streaming. It also provides configurable options for `retryDelay` to manage client reconnection logic and `highWaterMark` to control internal stream buffering, offering fine-grained control over SSE behavior.
Common errors
-
Error: fastify.sse is not a function
cause The `fastify-sse-v2` plugin was either not registered correctly, or your Fastify instance is too old for the plugin version used.fixEnsure you have called `fastify.register(FastifySSEPlugin);` before attempting to use `reply.sse()`. Also, verify that your Fastify version meets the peer dependency requirement (>=4.0.0 for plugin versions 3.x and above). -
ERR_STREAM_WRITE_AFTER_END
cause Attempting to write data to an SSE stream that has already been closed, either explicitly by calling `reply.sseContext.source.end()` or due to client disconnection.fixImplement checks to ensure the SSE stream is still active before attempting to send new events. For individual events, manage the lifecycle with `reply.sseContext.source.end()`. For `AsyncIterable` or `EventEmitter` sources, ensure your generator or event listener cleans up when the `request.socket.on('close')` event fires. -
ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client
cause You are attempting to send HTTP headers (e.g., calling `reply.send()` or similar methods) after the SSE connection has been initiated, which already sends specific headers for SSE.fixOnce `reply.sse()` is called, the response is committed to an SSE stream. Do not attempt to modify headers or send a different type of response on the same request. Ensure all SSE-related logic is handled within the `reply.sse()` context.
Warnings
- breaking The 'end' event, previously emitted when an SSE stream was closing, has been removed. Dependents on this event for cleanup or notification will need to find alternative mechanisms, such as listening to `request.socket.on('close')`.
- breaking Version 3.0.0 dropped support for Fastify v3. This plugin now exclusively requires Fastify v4 or newer. Attempting to use it with Fastify v3 will result in compatibility issues.
- gotcha When sending individual events using `reply.sse()`, the connection remains open indefinitely. It will only terminate if `reply.sseContext.source.end()` is explicitly called or the client disconnects.
- gotcha Exceptions thrown after headers have been sent in the SSE context can lead to unexpected behavior or unhandled errors. Version 4.2.2 introduced a fix for this scenario.
- gotcha Prior to version 4.2.1, newlines within event data were not handled properly, potentially corrupting the SSE stream format. This was addressed in `v4.2.1`.
Install
-
npm install fastify-sse-v2 -
yarn add fastify-sse-v2 -
pnpm add fastify-sse-v2
Imports
- FastifySSEPlugin
const FastifySSEPlugin = require('fastify-sse-v2');import { FastifySSEPlugin } from 'fastify-sse-v2'; - sse
reply.sse({ data: 'hello' }); - FastifyReply and SSE types
import { FastifyInstance, FastifyReply } from 'fastify'; declare module 'fastify' { interface FastifyReply { sse: (payload: { id?: string; event?: string; data: string; retry?: number; comment?: string } | AsyncIterable<{ id?: string; event?: string; data: string; retry?: number; comment?: string }>) => void; sseContext: { source: { end: () => void } }; } }
Quickstart
import Fastify from 'fastify';
import { FastifySSEPlugin } from 'fastify-sse-v2';
const fastify = Fastify({ logger: true });
fastify.register(FastifySSEPlugin);
// Helper to simulate async work
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
fastify.get('/events', function (request, reply) {
reply.sse(
(async function* source() {
for (let i = 0; i < 5; i++) {
await sleep(1500);
const eventData = { id: String(i), data: `Message ${i} at ${new Date().toISOString()}` };
fastify.log.info(`Sending event: ${JSON.stringify(eventData)}`);
yield eventData;
}
fastify.log.info('SSE stream finished.');
})()
);
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();