← Blog

Claude API Streaming Response in Node.js: Full Guide

2026-10-06 · 5 min read · SubToAPI Team

If you're building a chat interface, a CLI tool, or any product that needs to show Claude's response token-by-token instead of waiting for the full reply, you need streaming. In Node.js, streaming a Claude API response means consuming a server-sent events (SSE) connection and writing text chunks to your UI, terminal, or HTTP response as they arrive, rather than buffering the entire completion before doing anything with it.

This matters for two practical reasons: perceived latency and memory. A non-streaming request to a model generating a few thousand tokens can take 10-30 seconds before you see anything. Streaming gets the first tokens to the user in under a second. It also lets you process long outputs incrementally instead of holding a huge string in memory until the model finishes.

How streaming actually works

Claude's API (and compatible gateways like SubToAPI) stream responses using SSE over a standard HTTP connection. Instead of one JSON blob, the server sends a sequence of events — each one a small JSON chunk — terminated by a final event marking completion. Your Node.js client reads these events off the response stream as they arrive and appends the text deltas to build the full message.

The key technical point: this is not WebSockets, and you don't need a WebSocket library. It's a regular HTTPS request where the response body is kept open and fed to you incrementally. Node's built-in fetch (Node 18+) or https module handles this natively — no extra dependencies required for the transport layer, though the official Anthropic SDK and SubToAPI's client wrap the parsing for you.

Streaming with fetch and async iteration

The cleanest way to consume a stream in modern Node.js is with the Web Streams API, which fetch returns by default:

const response = await fetch('https://api.subtoapi.app/v1/messages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.SUBTOAPI_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    stream: true,
    messages: [{ role: 'user', content: 'Explain event loops in Node.js' }]
  })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop(); // keep incomplete line for next chunk

  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    const payload = line.slice(6);
    if (payload === '[DONE]') continue;

    const event = JSON.parse(payload);
    if (event.type === 'content_block_delta') {
      process.stdout.write(event.delta.text);
    }
  }
}

A few things to note here that trip people up:

Streaming to an HTTP response in Express

If you're building an API endpoint that proxies Claude's stream to a browser client, pipe the chunks through as SSE yourself:

app.post('/chat', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const upstream = await fetch('https://api.subtoapi.app/v1/messages', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SUBTOAPI_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'claude-sonnet-4-5',
      max_tokens: 1024,
      stream: true,
      messages: req.body.messages
    })
  });

  const reader = upstream.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    res.write(decoder.decode(value, { stream: true }));
  }

  res.end();
});

This forwards raw SSE straight to the browser, where EventSource or a fetch reader on the frontend can consume it the same way. Note that res.flushHeaders() or disabling compression middleware (compression()) for this route may be necessary — gzip buffering can silently defeat streaming and make your "streamed" response arrive all at once anyway.

Common mistakes that break streaming

A few issues show up repeatedly when people implement this:

Using a managed gateway instead

Implementing SSE parsing, reconnect logic, and buffer handling correctly is doable but easy to get subtly wrong, especially under load with many concurrent streams. If you want the API shape without managing the streaming plumbing yourself, SubToAPI turns your existing Claude access into an HTTPS API with sub_live_... keys, and exposes the same streaming behavior described above through a standard endpoint — see the streaming docs and the quickstart for a working example. It also gives you usage metadata per request and team seats if multiple people or services need access, which is useful once streaming moves from a prototype into something customer-facing. Plans start at pricing, with a free trial at signup.

Questions

Does streaming reduce total response time for Claude API calls? No — total generation time is roughly the same. Streaming reduces perceived latency by showing the first tokens almost immediately instead of making the user wait for the entire response to finish generating.

Can I use WebSockets instead of SSE for Claude streaming in Node.js? Claude's API streams over SSE, not WebSockets. You can wrap an SSE stream inside a WebSocket on your own backend if you need bidirectional messaging, but the upstream connection to Claude itself is SSE-based.

Why does my Node.js stream arrive all at once instead of incrementally? This is almost always a buffering issue — either compression middleware, a reverse proxy, or CDN buffering the response before forwarding it. Disable buffering for that route/path and confirm stream: true is set in the request body.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →