← Blog

Claude API Streaming Response in Node.js: Full Example

2026-11-03 · 5 min read · SubToAPI Team

If you're building a chat UI, a CLI tool, or any product where users wait for Claude's output, you want tokens appearing as they're generated instead of staring at a spinner until the full response lands. That's what streaming does, and in Node.js you have two practical ways to get it: the official Anthropic SDK with its built-in stream helper, or a raw fetch call that parses Server-Sent Events (SSE) yourself.

This article walks through both approaches with working code, explains the event types you'll see in the stream, and covers the error-handling and backpressure issues that trip people up in production.

Why stream at all

Without streaming, your Node process sends a request and blocks until Claude finishes generating the entire response — which for longer outputs can be several seconds or more. Streaming returns partial content as it's generated, so you can:

The tradeoff is complexity: you need to handle a chunked, event-based response instead of a single JSON payload.

Option 1: using the official Anthropic SDK

The @anthropic-ai/sdk package exposes a .stream() helper that abstracts away the SSE parsing for you.

npm install @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

async function streamResponse() {
  const stream = anthropic.messages.stream({
    model: "claude-opus-4-20250514",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Explain event loops in Node.js" }],
  });

  stream.on("text", (textDelta) => {
    process.stdout.write(textDelta);
  });

  const finalMessage = await stream.finalMessage();
  console.log("\n--- done ---");
  console.log(finalMessage.usage);
}

streamResponse();

The .on("text", ...) listener fires for every incremental chunk of generated text. stream.finalMessage() resolves once the stream closes, giving you the assembled message object including token usage — useful if you're tracking cost per request.

This is the recommended approach for most Node.js apps: fewer moving parts, built-in reconnection handling, and typed events.

Option 2: raw fetch and manual SSE parsing

Sometimes you don't want the SDK dependency, or you're calling an API endpoint that speaks SSE directly (which is also how you'd stream from a proxy like SubToAPI). Here's the manual version using Node's native fetch:

async function streamWithFetch() {
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
    },
    body: JSON.stringify({
      model: "claude-opus-4-20250514",
      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 the incomplete line for the next chunk

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

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

streamWithFetch();

The key events you'll see in the raw SSE stream:

Buffering partial lines matters here — chunks from the network don't align with SSE message boundaries, so you need to hold onto an incomplete line until the next read.

Handling errors and disconnects

Streaming requests fail differently than normal requests. A connection can drop mid-stream, or the API can send an error event instead of message_stop. Always:

If you're running Claude access through an API wrapper rather than calling Anthropic directly, confirm the wrapper forwards SSE events unmodified rather than buffering the whole response before sending — otherwise you lose the point of streaming.

Streaming through SubToAPI

If you're exposing Claude access to your own app, internal tools, or a team of developers, calling Anthropic's API directly from every client means managing raw API keys and writing this SSE-handling code in every service. SubToAPI turns your existing Claude access into an HTTPS API with scoped sub_live_... keys, and streaming works the same way shown above — just point your request at https://api.subtoapi.app/v1/messages with stream: true and an Authorization: Bearer $SUBTOAPI_KEY header. The event format matches what's documented in /docs/streaming, so the parsing code in this article works unchanged. Usage metadata per request is also returned, which is handy if you're billing seats or tracking cost by application key — see /docs/messages for the full request/response shape.

Wrapping up

For most Node.js projects, start with the official SDK's .stream() helper — it handles reconnection and parsing for you. Reach for raw fetch and manual SSE parsing when you need full control over the transport, or when you're building a proxy layer yourself. Either way, buffer partial chunks correctly, watch for error events, and always check stop_reason so you know why a generation ended.

questions

Does Claude's API support streaming out of the box? Yes. Set "stream": true in the request body and the API returns a Server-Sent Events stream instead of a single JSON response, with incremental content_block_delta events carrying text.

Can I stream tool use (function calling) responses the same way? Yes, tool use streams through the same event types — content_block_start marks the beginning of a tool call block, and content_block_delta events carry partial JSON for the tool input as it's generated. See /docs/tools for the full event sequence.

What's the fastest way to add streaming without writing SSE parsing code myself? Use the official @anthropic-ai/sdk package's .stream() method, which handles event parsing, reconnection, and gives you a finalMessage() promise for the completed response — no manual buffer handling required.

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 →