Stream AI Tokens to Your Terminal with Node.js SSE
Stream AI Tokens to Your Terminal with Node.js SSE
A loading spinner tells you almost nothing when an AI request takes ten seconds. Streaming lets your interface show text as it arrives. Server-sent events, or SSE, provide a straightforward transport: one HTTP response stays open while the server writes small text frames. We will build a Node.js endpoint that relays model output through Fluxo and watch it arrive with curl.
Create the Node.js streaming project
Use Node.js 22 or newer and Bash for these commands. Check your runtime, create a directory, and install the OpenAI SDK: — node --version
mkdir fluxo-sse && cd fluxo-sse — npm init -y
npm install openai — Fluxo gives you one key and one base URL, https://api.fluxorelay.com, across Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok and more. That fits an editor-and-terminal workflow: the same service works with Claude Code, Codex, Gemini CLI and any OpenAI SDK.
If you would like to follow along, create a Fluxo account and copy your API key.
Read the key without placing it directly in shell history:
read -rsp "Fluxo API key: " FLUXO_API_KEY; export FLUXO_API_KEY; printf "\n"
Choose GPT-6 Astra as the concrete example, then copy its exact API model identifier from Fluxo. Display names and API identifiers can differ, so paste the identifier at this prompt:
read -rp "Fluxo model ID: " FLUXO_MODEL; export FLUXO_MODEL
Write the SSE relay in server.mjs
This command writes the complete server. Paste it as one shell command. The SDK uses the OpenAI-compatible /v1 API path beneath Fluxo’s base URL.
node --input-type=module -e 'import { writeFileSync } from "node:fs"; writeFileSync("server.mjs", String.raw`import http from "node:http"; import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.FLUXO_API_KEY, baseURL: "https://api.fluxorelay.com/v1" }); if (!process.env.FLUXO_MODEL) throw new Error("Set FLUXO_MODEL first"); http.createServer(async (req, res) => { if (req.method !== "GET" || req.url !== "/events") { res.writeHead(404); res.end("Use GET /events"); return; } const controller = new AbortController(); res.on("close", () => controller.abort()); res.writeHead(200, { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no" }); res.flushHeaders(); const send = (event, data) => res.write("event: " + event + "\ndata: " + JSON.stringify(data) + "\n\n"); const heartbeat = setInterval(() => res.write(": heartbeat\n\n"), 15000); try { const stream = await client.chat.completions.create({ model: process.env.FLUXO_MODEL, messages: [{ role: "user", content: "Explain server-sent events in five short sentences." }], stream: true }, { signal: controller.signal }); for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content; if (text) send("token", { text }); } if (!res.destroyed) send("done", {}); } catch (error) { if (!controller.signal.aborted) { console.error(error); if (!res.destroyed) send("failure", { message: "Upstream request failed" }); } } finally { clearInterval(heartbeat); res.end(); } }).listen(3000, "127.0.0.1", () => console.log("Listening on http://127.0.0.1:3000/events"));`);'
Each SSE frame ends with two newline characters. JSON serialization keeps embedded newlines inside generated text from breaking that framing. The upstream SDK parses the provider stream; our server emits its own token, done and failure events.
The heartbeat is an SSE comment, which keeps otherwise idle connections active without becoming a token event. Closing curl aborts the upstream request and clears the timer.
Watch incremental events with curl
Start the server in your current terminal: — node server.mjs
Open another terminal and run: — curl -N --fail-with-body http://127.0.0.1:3000/events
The -N option disables curl’s output buffering. You should see token events containing JSON text fragments, followed by a done event. These fragments are delivery chunks; they do not necessarily correspond to individual billing tokens. Press Ctrl+C to exercise cancellation.
Check SSE buffering before deploying
- Disable response buffering and compression for this streaming route.
- Configure proxy idle timeouts to exceed the heartbeat interval.
- Keep your Fluxo API key exclusively on the server.
- Add authentication and request limits before exposing this endpoint publicly.
Once headers are flushed, upstream failures cannot change the HTTP status. Clients must handle the failure event as well as network errors. Browser clients should close their EventSource after done or failure to prevent automatic reconnection from starting another generation.
Fluxo bills per real token, including cache reads and writes, with no monthly fee. Account for that when adding retries. Users also earn a referral share when people they invite pay. If you need help checking your model identifier or connection, leave a note on the contact page after signing in.