Stream a model response with JavaScript

Read a chat completion stream using server-side JavaScript, display text chunks, capture usage and avoid retrying a partially delivered answer.

By Jevrouter · Updated

Keep the API key on your server

Use this example in a Node.js server process, not in a public browser bundle. A browser application should call your authenticated backend, which applies its own access and spending controls before calling Jevrouter.

The example uses the OpenAI JavaScript SDK and server-sent events. Streaming improves the time until a user can start reading; it does not imply lower total cost or a faster completed answer.

Shell
npm install openai
export JEVROUTER_API_KEY='YOUR_PROJECT_API_KEY'

Read text and usage from the stream

Set stream_options.include_usage to request the final usage information. The usage chunk can have an empty choices array, so read usage independently from text. max_retries is disabled to keep retry decisions in your application.

JavaScript
import OpenAI from "openai";
import { randomUUID } from "node:crypto";

const client = new OpenAI({
  baseURL: "https://api.jevrouter.io/v1",
  apiKey: process.env.JEVROUTER_API_KEY,
  maxRetries: 0,
  timeout: 125_000,
});

const stream = await client.chat.completions.create({
  model: "openai/gpt-4.1-mini",
  messages: [{ role: "user", content: "Explain an idempotency key in three sentences." }],
  max_tokens: 512,
  stream: true,
  stream_options: { include_usage: true },
}, { headers: { "Idempotency-Key": randomUUID() } });

for await (const chunk of stream) {
  const choice = chunk.choices[0];
  if (choice?.delta?.content) process.stdout.write(choice.delta.content);
  if (choice?.finish_reason) console.error("Finish:", choice.finish_reason);
  if (chunk.usage) console.error("Usage:", chunk.usage);
}
process.stdout.write("\n");

Treat a partial answer as a distinct result

When streaming fails after content has arrived, keep the partial result and request identifier. Do not append the output of a second model to the first model's answer. Jevrouter does not transparently switch models after committing the stream.

User cancellation should abort your upstream request too. The request can still have billable usage if the supplier already produced content. Check the settled request record rather than assuming that closing the connection makes it free.

Separate tool calls from display text

This short example displays text only. Tool calls arrive as incremental arguments; applications must assemble them, validate the complete JSON and apply their own authorization before executing a tool. Model-generated arguments are untrusted input.

Measure both time to first token and time to a complete accepted result. A fast first chunk can hide a slow or truncated completion, and the source catalog does not supply a Jevrouter latency guarantee.

Models mentioned in this guide

Try it in your project.

Choose a model, create a scoped API key and inspect the result in Requests.

Open console ↗