59API

← Volver a las guías

Stream LLM Responses in React: A Fetch-First Guide

API · EN · 2026-10-30

Build the smallest useful streaming pipeline

Streaming LLM responses in React means displaying generated text as it arrives instead of waiting for the complete answer. It improves perceived responsiveness, but does not necessarily reduce total generation time.

The simplest secure architecture is React → your backend → 59API. Your backend keeps the API key private, consumes the provider stream, and forwards small updates to the browser. This guide uses an OpenAI-compatible SDK upstream and newline-delimited JSON downstream, avoiding a handwritten server-sent events parser.

1. Configure your backend

Use Node.js 20 or later and install dependencies with npm install express openai. Enable ES modules in your project. Store your credentials in server-side environment variables, never in a frontend bundle or a VITE-prefixed variable.

Set API_BASE_URL to the OpenAI-compatible base shown in your 59API dashboard. The service host is https://api.59api.com; confirm whether your SDK configuration requires a /v1 suffix. Set LLM_MODEL to the exact API model identifier from the dashboard, not an assumed slug based on its display name.

59API offers cheap, pay-as-you-go access to native, official-quality models without downgrades. You could evaluate DeepSeek V4 Flash for a cost-conscious chat interface or Claude Sonnet 5.5 for more demanding development workflows. Compare their latency and pricing against your actual prompts.

2. Normalize the upstream stream

Create a server entry file with the following code. Each downstream line is one complete JSON object. Delta records carry text; error records distinguish a failed generation from a successful empty response.

import express from 'express'; import OpenAI from 'openai'; const app = express(); app.use(express.json({ limit: '32kb' })); const client = new OpenAI({ apiKey: process.env.API_KEY, baseURL: process.env.API_BASE_URL }); app.post('/api/chat', async (req, res) => { const prompt = req.body?.prompt; if (typeof prompt !== 'string' || !prompt.trim()) return res.status(400).json({ error: 'Prompt required' }); const controller = new AbortController(); res.on('close', () => controller.abort()); try { const stream = await client.chat.completions.create({ model: process.env.LLM_MODEL, messages: [{ role: 'user', content: prompt }], stream: true }, { signal: controller.signal }); res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8'); res.setHeader('Cache-Control', 'no-cache, no-transform'); res.flushHeaders(); for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content; if (text) res.write(JSON.stringify({ delta: text }) + '\n'); } } catch { if (!controller.signal.aborted) { if (!res.headersSent) res.status(502).json({ error: 'Generation failed' }); else res.write(JSON.stringify({ error: 'Generation interrupted' }) + '\n'); } } finally { res.end(); } }); app.listen(3001);

Configure your development proxy to forward /api requests to port 3001. In production, route that path through your application server. Keeping requests same-origin avoids unnecessary CORS configuration.

3. Read incremental updates in React

Import useState and useRef from React. Place these declarations and the send function inside your component. Connect send(prompt) to form submission, render text as ordinary React text, and disable submission while busy.

const [text, setText] = useState(''); const [error, setError] = useState(''); const [busy, setBusy] = useState(false); const abortRef = useRef(null); async function send(prompt) { const controller = new AbortController(); abortRef.current = controller; setText(''); setError(''); setBusy(true); try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt }), signal: controller.signal }); if (!response.ok || !response.body) throw new Error('Request failed'); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); let pending = ''; while (true) { const { value, done } = await reader.read(); if (done) break; pending += value; let boundary; while ((boundary = pending.indexOf('\n')) !== -1) { const line = pending.slice(0, boundary); pending = pending.slice(boundary + 1); if (!line.trim()) continue; const event = JSON.parse(line); if (event.error) throw new Error(event.error); if (event.delta) setText(previous => previous + event.delta); } } } catch (err) { if (!controller.signal.aborted) setError(err.message); controller.abort(); } finally { setBusy(false); } }

Connect a Stop button to abortRef.current?.abort(), and display error separately from generated text. Also abort on component unmount. TextDecoderStream preserves UTF-8 characters across byte boundaries; the pending buffer preserves JSON records across network chunks. A network chunk is not necessarily one token or one record.

4. Check before shipping

If you want inexpensive access to multiple model families through one integration, consider signing up for 59API and testing this pipeline with a small usage budget.

Precios · Docs

¿Listo para empezar?

Conecta Claude y GPT en minutos a los precios más bajos, sin recortes. Regístrate para obtener tu clave API.

Registro gratis