n4nAI

Cloudflare Workers Streams API for token-by-token LLM output

Implement the cloudflare workers streams api llm tokens pattern to proxy OpenAI-compatible streaming endpoints with low latency and full backpressure control.

n4n Team3 min read677 words

Audio narration

Coming soon — every post will get a voice note here.

The cloudflare workers streams api llm tokens approach turns a Worker into a thin, low-latency proxy that forwards model output to clients the instant each token is generated. Instead of buffering a full completion, you pipe the provider’s Server-Sent Events (SSE) through Web Streams primitives and emit cleaned token frames. This guide walks through a production-shaped implementation on Cloudflare Workers, from wrangler init to a verified curl stream.

Step 1: Scaffold the Worker project

Start with a TypeScript Worker. Wrangler handles bundling and the Streams API is available globally at runtime—no polyfills needed.

npm create cloudflare@latest llm-stream-worker
cd llm-stream-worker
wrangler init --from-dash

Pick the “Hello World” Worker template (ES modules format). Your src/index.ts will export a fetch handler. Add a secret for the upstream API key:

wrangler secret put UPSTREAM_API_KEY

Store the base URL and model in wrangler.toml or env vars. For local dev, wrangler dev serves on localhost:8787.

Step 2: Issue a streaming request to the LLM

Cloudflare Workers use the standard fetch API. Set stream: true in the chat completion body. The upstream must be OpenAI-compatible; if you route through n4n.ai, the same endpoint works across 240+ models with automatic fallback when a provider is degraded.

interface Env {
  UPSTREAM_API_KEY: string;
  UPSTREAM_BASE: string; // e.g. https://api.openai.com/v1 or https://api.n4n.ai/v1
  MODEL: string;
}

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    const upstreamResp = await fetch(`${env.UPSTREAM_BASE}/chat/completions`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: `Bearer ${env.UPSTREAM_API_KEY}`,
      },
      body: JSON.stringify({
        model: env.MODEL,
        stream: true,
        messages: [{ role: "user", content: "Explain backpressure in 3 sentences." }],
      }),
    });

    if (!upstreamResp.ok || !upstreamResp.body) {
      return new Response("Upstream error", { status: 502 });
    }
    // Stream wiring continues below
  },
};

The cloudflare workers streams api llm tokens pattern depends on upstreamResp.body being a ReadableStream<Uint8Array>. If the provider returns non-streaming JSON, this code will not work—verify the stream: true flag is honored.

Step 3: Parse SSE with a TransformStream

OpenAI-style streams are newline-delimited data: {json} frames. A TransformStream lets you decode bytes, split lines, and extract delta.content. The transform respects backpressure: if the client is slow, controller.enqueue blocks until the downstream catches up.

function parseTokenStream(): TransformStream<Uint8Array, Uint8Array> {
  const decoder = new TextDecoder();
  const encoder = new TextEncoder();
  let buffer = "";

  return new TransformStream({
    transform(chunk, controller) {
      buffer += decoder.decode(chunk, { stream: true });
      const lines = buffer.split("\n");
      buffer = lines.pop() ?? ""; // keep partial line
      for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed.startsWith("data: ")) continue;
        const payload = trimmed.slice(6).trim();
        if (payload === "[DONE]") return;
        try {
          const json = JSON.parse(payload);
          const token: string | undefined = json.choices?.[0]?.delta?.content;
          if (token) controller.enqueue(encoder.encode(token));
        } catch {
          // ignore malformed keep-alive lines
        }
      }
    },
    flush(controller) {
      if (buffer.trim().length) {
        // attempt final parse on disconnect
      }
    },
  });
}

Pipe the upstream body through this transform and re-encode for the browser:

const tokenStream = upstreamResp.body
  .pipeThrough(parseTokenStream());

return new Response(tokenStream, {
  headers: {
    "content-type": "text/plain; charset=utf-8",
    "cache-control": "no-store",
    "x-accel-buffering": "no", // disable proxy buffering
  },
});

Step 4: Return a client-safe stream

Browsers and fetch clients treat the response body as a stream automatically. Set content-type to text/plain or application/octet-stream. Avoid text/event-stream unless you are forwarding raw SSE—our transform already extracted tokens, so a flat byte stream is simpler to consume with response.body.getReader().

If you need JSON frames per token, change the enqueue to encoder.encode(JSON.stringify({ token }) + "\n"). That keeps the cloudflare workers streams api llm tokens output parseable line-by-line.

Step 5: Handle cancellation and errors

Workers abort the fetch handler when the client disconnects. Pass an AbortSignal to the upstream request so the provider stops generating:

const ac = new AbortController();
req.signal.addEventListener("abort", () => ac.abort());

const upstreamResp = await fetch(url, {
  method: "POST",
  headers,
  body: JSON.stringify({ model, stream: true, messages }),
  signal: ac.signal,
});

Inside the TransformStream, wrap controller.enqueue in try/catch. If the downstream reader cancels, the stream will throw and you should swallow it. Never await long-running work in transform—keep it synchronous to avoid head-of-line blocking.

For auth failures, check upstreamResp.status before piping. If you get a 429, the Worker can retry with a different model or return a 503. The cloudflare workers streams api llm tokens design keeps the Worker stateless, so retries are just new fetches.

Step 6: Deploy and verify

Publish the Worker:

wrangler deploy

Then hit the endpoint with a raw HTTP client that disables buffering:

curl -N https://llm-stream-worker.your-subdomain.workers.dev/

You should see tokens printed one by one with no delay between the full response. If you get a single buffered blob, check that x-accel-buffering: no is set and that no CDN in front of the Worker is aggregating chunks.

To verify programmatically, use a small Node script:

const resp = await fetch("https://llm-stream-worker.your-subdomain.workers.dev/");
const reader = resp.body!.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  process.stdout.write(decoder.decode(value));
}

If tokens arrive incrementally and the process exits after [DONE] (or stream close), the pipeline is correct.

Why not use a higher-level SDK?

The AI SDKs are convenient but hide the stream lifecycle. When you need to add rate-limit headers, inject per-token logging, or fork the stream to a second sink (e.g., a metrics pipe), the raw cloudflare workers streams api llm tokens primitives are clearer. TransformStream is also CPU-cheap: no intermediate string arrays beyond the line buffer, and V8 optimizes the TextDecoder path.

One caveat: Workers have a 30-second CPU limit on the free tier and 5 minutes on paid. Streaming responses do not count against CPU time while waiting on I/O, but your transform runs on the event loop per chunk. Keep parsing minimal—no regex heavier than split and startsWith.

Production notes

  • Set cache-control: no-store on the response; LLM output is user-specific.
  • If you forward provider cache-control hints, honor them on the Worker response to leverage edge caching for identical prompts.
  • Use env.MODEL indirection so you can shift traffic between models without redeploying.
  • For multi-tenant auth, validate a JWT in the Worker before calling the upstream; do not expose your UPSTREAM_API_KEY to the browser.

The pattern scales horizontally because each Worker instance holds only open sockets and a few kilobytes of stream state. That is the entire footprint for token-by-token LLM delivery.

Tagscloudflare-workersstreams-apistreamingllm-api

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All cloudflare workers llm integration posts →