Elegant Server-Sent Events (SSE) Streaming for Cloudflare Workers.
- Provides a simple, straightforward, and functional approach to streaming SSE messages using async generators.
- Easily integrates with existing Workers.
- Works seamlessly with OpenAI streaming.
- Adheres to the SSE specification and is thoroughly tested.
With npm
npm install cloudflare-workers-sse
With Yarn
yarn add cloudflare-workers-sse
With pnpm
pnpm add cloudflare-workers-sse
With Bun
bun add cloudflare-workers-sse
The implementation of SSE involves two components: a client that receives messages and a server (in this case, a worker) that publishes them.
Let's start with the worker.
import { sse } from "cloudflare-workers-sse";
export default {
fetch: sse(handler)
};
async function* handler(request: Request, env: Env, ctx: ExecutionContext) {
yield {
event: "greeting",
data: { text: "Hi there!" }
};
}
And that's basically it. All messages yielded are streamed to a client listening for them. Once there are no more messages, the stream is closed.
Although the simplest client-side implementation can be achieved using EventSource
, for more advanced scenarios — such as using POST
requests or handling authentication — it is recommended to use libraries such as @microsoft/fetch-event-source
.
const eventSource = new EventSource("https://<YOUR_WORKER_SUBDOMAIN>.workers.dev");
eventSource.addEventListener("greeting", (event) => {
// handle the greeting message
});
All messages yielded by a handler should conform to the SSEMessage
interface.
interface SSEMessage {
id?: string;
event?: string;
data?: null | boolean | number | bigint | string | Jsonifiable;
}
data
is optional and can be any primitive type (except Symbol
) or an object, in which case it will be converted to JSON. More information about Jsonifiable
can be found here. If data is omitted or set to undefined
or null
, the empty data
field will be added.
Errors thrown by a handler can be caught using the onError
callback, which can also return a message to be streamed to the client before closing the stream.
import { sse } from "cloudflare-workers-sse";
export default {
fetch: sse(handler, {
onError: (error, request, env, ctx) => ({ event: "error_occurred" })
})
}
By default, only essential headers such as Content-Type
, Cache-Control
, and Connection
are included in the response. To send additional headers, use the customHeaders
option.
import { sse } from "cloudflare-workers-sse";
export default {
fetch: sse(handler, {
customHeaders: { "access-control-allow-origin": "https://example.com" }
})
}
If your worker requires additional logic — such as request validation, response modification, or other pre/post-processing — you can implement a middleware.
import { type FetchHandler, sse } from "cloudflare-workers-sse";
export default {
fetch: middleware(sse(handler))
}
function middleware<Env>(
nextHandler: FetchHandler<Env>
): FetchHandler<Env> {
return async function middlewareHandler(
request: Request,
env: Env,
ctx: ExecutionContext
) {
// a before logic
const response = await nextHandler(request, env, ctx);
// an after logic
return response;
};
}