Streaming Claude Tokens with Fastify on Lambda: A Step‑By‑Step Guide Explained Simply A developer published a step-by-step guide showing how to stream Claude tokens to the browser using a minimal Fastify service on AWS Lambda, calling Bedrock through Node.js 22's native fetch and forwarding tokens as Server-Sent Events. The approach avoids Express middleware overhead and heavy HTTP client dependencies to cut per-token latency and Lambda cold-start time. LLM developers assume they need a heavyweight framework to handle streaming responses, but Fastify’s tiny core can proxy Claude’s token stream with almost zero overhead. In this post you’ll see how to turn a Fastify service into a real‑time chat endpoint that streams tokens directly to the browser. When you ask a language model LLM like Claude to generate text, it often returns tokens the smallest pieces of language one by one. To give a user a “live‑typing” feel you need to forward each token to the browser as soon as it arrives. Express —the long‑time favorite of Node developers—adds a lot of middleware layers. Those layers are great for flexibility, but each extra function call adds a tiny amount of latency. When you are forwarding dozens of tokens per second, that latency compounds and you can notice a pause between the model’s output and what the user sees. Fastify is built around performance first principles: Think of Fastify as a high‑speed conveyor belt that moves a package the token from the model to the client with almost no friction, whereas Express is more like a busy hallway where the same package has to stop at several doors before continuing. For a streaming chat endpoint, that difference is noticeable. In plain English – Fastify’s thin design means less time waiting inside the server, so the user sees tokens almost as soon as Claude produces them. python // src/server.ts import Fastify from "fastify"; // Create a Fastify instance with the smallest possible options const fastify = Fastify { logger: true, // helpful logs, can be turned off later // No plugins loaded – we keep the core tiny } ; fastify.get "/", async request, reply = { // A simple health‑check endpoint return { status: "ok" }; } ; // Export the server so Lambda can import it later export default fastify; Fastify creates the server. logger: true prints request details – useful while developing. Node.js 22 ships with a native fetch implementation that works like the browser’s fetch . That means we can call AWS Bedrock the service that hosts Claude without pulling in a heavy HTTP client library. Using native fetch keeps the bundle size tiny, which is important for Lambda where every kilobyte counts toward cold‑start time. Why avoid extra libraries? Every additional dependency must be zipped, uploaded, and unpacked when Lambda starts. The more code you ship, the longer the cold start the first time your function runs after being idle . Native fetch eliminates that extra work. js // src/routes/chat.ts import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; export async function chatRoute fastify: FastifyInstance { // Register a POST /chat endpoint fastify.post "/chat", async request: FastifyRequest, reply: FastifyReply = { // Parse the incoming JSON body { prompt: "..." } const { prompt } = request.body as { prompt: string }; if prompt { reply.status 400 .send { error: "Prompt is required" } ; return; } // Call the helper that talks to Bedrock defined later const stream = await invokeClaudeStream prompt ; // Set up Server‑Sent Events SSE headers – see next section for details reply.raw.writeHead 200, { "Content-Type": "text/event-stream", // tells browser to treat data as a stream "Cache-Control": "no-cache", Connection: "keep-alive", } ; // Pipe each token from Bedrock to the client as an SSE line for await const token of stream { // SSE format: each message starts with "data:" and ends with two newlines reply.raw.write data: ${JSON.stringify { token } }\n\n ; } // Close the SSE stream when Bedrock finishes reply.raw.end ; } ; } request.body holds the JSON payload the browser sends. invokeClaudeStream implemented later returns an reply.raw gives us access to the underlying Node http.ServerResponse so we can write raw SSE data. Tip – Keep fetch calls and response handling inside the route as short as possible . Anything that can be moved to a helper function reduces the amount of code that runs during each request, making the Lambda cold start faster. Bedrock is Amazon’s managed service that hosts LLMs like Claude. The SDK package we use is @aws-sdk/client-bedrock-runtime . Inside the SDK there is a method called InvokeModelCommand that can be asked to stream the model’s output instead of waiting for the whole answer. Imagine Claude is a storyteller reading a book aloud. With normal non‑streaming calls, you wait until the storyteller finishes the whole chapter before you hear anything. With streaming, you hear each sentence as soon as it is spoken. In technical terms, the service sends a chunked HTTP response where each chunk contains the next token. js // src/bedrock.ts import { BedrockRuntimeClient, InvokeModelCommand, } from "@aws-sdk/client-bedrock-runtime"; import { Readable } from "stream"; // Create a client that uses the default Lambda credentials const bedrock = new BedrockRuntimeClient {} ; // Helper that returns an async iterable of tokens export async function invokeClaudeStream prompt: string : AsyncGenerator