Server-Sent Events with Fetch API
fetch-event-stream is a lightweight (741 bytes gzipped) utility designed to parse Server-Sent Events (SSE) from `fetch` responses using the native Web Streams API. It's currently at version 0.1.6 and has a frequent patch release cadence, addressing compatibility and feature enhancements. Unlike the browser's native `EventSource` which is limited to GET requests and lacks custom headers, `fetch-event-stream` allows any HTTP method (e.g., POST for APIs like Anthropic or OpenAI) and supports custom headers and JSON payloads, making it suitable for modern API interactions that often require authentication. It differentiates itself by leveraging native Web Streams without heavy polyfills, ensuring a small bundle size and broad compatibility across browsers, Node.js, Deno, Bun, Cloudflare Workers, and Web/Service Workers. This approach avoids the common pain point of bloated SDKs, such as the `openai` library's larger size due to stream polyfills, offering a more streamlined and performant solution for SSE consumption.
Common errors
-
ReferenceError: require is not defined
cause Attempting to import `fetch-event-stream` using CommonJS `require()` syntax in an ESM context (e.g., in a Node.js project with `"type": "module"` or a browser environment).fixUse ESM `import` syntax: `import { events, stream } from 'fetch-event-stream';`. -
Uncaught (in promise) Response { status: 401, statusText: "Unauthorized", ... }cause The `stream()` function threw an HTTP `Response` object because the API request resulted in a non-`2xx` status code (e.g., 401 Unauthorized, 404 Not Found, 500 Internal Server Error).fixWrap the `stream()` call in a `try...catch` block and specifically handle `Response` errors: `try { ... } catch (error) { if (error instanceof Response) { console.error('API Error:', error.status, await error.text()); } else { throw error; } }`. -
TypeError: (intermediate value)(intermediate value) is not async iterable
cause Attempting to use `for await (const event of stream)` on a `ReadableStream` in an environment (typically an older browser or specific runtime) that does not fully support asynchronous iteration of `ReadableStream` objects, potentially with an `fetch-event-stream` version prior to `0.1.4`.fixEnsure you are using `fetch-event-stream@0.1.4` or newer. If the problem persists, verify your runtime environment's Web Streams API compatibility. For extremely old environments, you might need to use `events()` with a custom stream reader.
Warnings
- gotcha Older browser environments (non-canary versions prior to `fetch-event-stream@0.1.4`) might not fully support async iteration (`for await (let ... of ...)`) of `ReadableStream`. Version `0.1.4` introduced an internal rewrite to address this compatibility.
- breaking The parsing logic for `event.id` was modified in `v0.1.6`. Previously, all `event.id` values were coerced to `number` if possible. Now, `event.id` is only converted to `number` if the value is *exactly* a number string (e.g., '123' -> 123). Strings like '003' will remain '003' (string), which might break existing numeric comparisons.
- gotcha The `stream()` convenience function will `throw` the raw `Response` object if the `fetch` call results in a non-`2xx` status code. This differs from standard `fetch` behavior where `response.ok` is false but the promise still resolves.
Install
-
npm install fetch-event-stream -
yarn add fetch-event-stream -
pnpm add fetch-event-stream
Imports
- events
const { events } = require('fetch-event-stream');import { events } from 'fetch-event-stream'; - stream
const { stream } = require('fetch-event-stream');import { stream } from 'fetch-event-stream';
Quickstart
import { stream } from 'fetch-event-stream';
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? '';
async function getOpenAIStream() {
try {
const events = await stream('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is the capital of France?' }
],
stream: true
})
});
for await (const event of events) {
if (event.data === '[DONE]') {
console.log('Stream finished.');
break;
}
try {
const parsedData = JSON.parse(event.data);
// Process your parsed SSE data here
console.log('Received:', parsedData);
} catch (e) {
console.error('Failed to parse event data:', event.data, e);
}
}
} catch (error) {
if (error instanceof Response) {
const errorText = await error.text();
console.error('API Error:', error.status, errorText);
} else {
console.error('An unexpected error occurred:', error);
}
}
}
getOpenAIStream();