Fluxio
← Voltar ao blog

Stream to the Browser with fetch and ReadableStream

SSE e APIsEN

§1 Define the Browser Streaming Contract

Build a small chat interface with a prompt field, a Send button, a Stop button, and an output paragraph. The goal is to display generated text progressively instead of waiting for the complete answer. A fetch response exposes its body as a ReadableStream, allowing your application to process incoming data incrementally. ([fetch.spec.whatwg.org](https://fetch.spec.whatwg.org/))

For this walkthrough, define a same-origin POST endpoint at /api/chat that returns UTF-8 plain text. Your backend handles the AI provider’s event format and forwards only extracted text. This keeps provider-specific parsing out of the browser and gives the frontend one predictable contract.

§2 Relay AI Output Through Your Backend

Keep the provider API key on your server. Have /api/chat validate the prompt, authenticate the user, and request streaming output upstream. For the streaming-first, pay-per-token relay described here, Fluxio is a natural fit: one key reaches multiple model families while responses arrive progressively. With an OpenAI-style client, configure its Fluxio base URL with the /v1 suffix.

Do not confuse transport chunks with complete messages or tokens. If your upstream uses server-sent events, parse its event framing before extracting text. SSE uses UTF-8, blank lines delimit events, and multiple data fields within an event are joined with newline characters. A network read is not an event boundary. ([html.spec.whatwg.org](https://html.spec.whatwg.org/multipage/server-sent-events.html?utm_source=openai))

Implement your backend relay so it writes each extracted text fragment promptly rather than accumulating the complete answer. Return text/plain; charset=utf-8 for this browser-facing contract, and inspect your hosting stack for response buffering.

§3 Start fetch with an AbortController

Create a fresh controller for every submission. Pass its signal to fetch so the Stop button can interrupt the request. Check the HTTP status explicitly: an HTTP error response does not automatically reject fetch. Also reject a response without a readable body. ([fetch.spec.whatwg.org](https://fetch.spec.whatwg.org/))

const controller = new AbortController(); const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt }), signal: controller.signal }); if (!response.ok) throw new Error('Request failed: ' + response.status); if (!response.body) throw new Error('Streaming body unavailable');

Place this logic inside your submission handler’s try block. Disable Send while the request is active, clear the previous answer, and keep the active controller accessible to the Stop handler.

§4 Decode UTF-8 and Read Each Chunk

Pipe the byte stream through TextDecoderStream before reading it. Streaming decoding preserves characters whose UTF-8 bytes are split across chunks; independently decoding every chunk can mishandle those boundaries. ([encoding.spec.whatwg.org](https://encoding.spec.whatwg.org/))

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); try { while (true) { const { value, done } = await reader.read(); if (done) break; output.appendChild(document.createTextNode(value)); } } finally { reader.releaseLock(); }

Here, output refers to your output paragraph. Each successful read provides another decoded fragment, while done indicates normal stream completion. Acquiring a reader locks the stream; releasing that lock is cleanup, not cancellation. ([streams.spec.whatwg.org](https://streams.spec.whatwg.org/))

§5 Handle Stop Actions and Interrupted Answers

Connect Stop to controller.abort(). In the outer catch block, distinguish an expected AbortError from a network or application failure. In the outer finally block, restore the buttons and clear the active request state. AbortController can interrupt fetch and response-body consumption. ([fetch.spec.whatwg.org](https://fetch.spec.whatwg.org/))

Preserve partial output when a request stops, but label it as stopped or interrupted rather than complete. If your server detects a browser disconnect, arrange for it to cancel upstream generation too. Avoid automatically retrying a partially displayed answer unless you have designed explicit resume behavior.

§6 Test Chunk Boundaries and Visible Delivery

Use a controlled test endpoint before connecting live generation. Send delayed fragments, vary their sizes, and deliberately split a multibyte character across byte chunks. Check these cases:

  • – Text appears before the response finishes.
  • – Characters remain intact across arbitrary chunk boundaries.
  • – Stopping a request leaves the interface ready for another submission.
  • – HTTP failures and midstream disconnects produce distinct interface states.
  • – The deployed proxy delivers fragments without waiting for completion.

Sign up for Fluxio to build your streaming AI interface.

Criar uma conta Fluxio →

Mais da corrente