← Blog

Building a Real-Time Chat App with the Claude API

2026-09-25 · 5 min read · SubToAPI Team

Building a real-time chat application with the Claude API means streaming tokens to the browser as they're generated, rather than waiting for a full response and dumping it on screen at once. The core building blocks are: a streaming endpoint that emits partial message chunks, a frontend that renders those chunks incrementally, and a conversation state layer that keeps track of message history between turns.

This is different from a typical "call an API, get JSON back" integration. Chat feels real-time when the first token appears in under a second and text keeps flowing continuously — like watching someone type. Getting this right requires understanding Claude's streaming format, handling reconnects and errors gracefully, and managing conversation history so each request includes the right context. Below is a practical walkthrough of the architecture and code.

Core Architecture

A real-time Claude chat app typically has three layers:

  1. Frontend — renders an input box, a message list, and appends streamed tokens to the last assistant message as they arrive.
  2. Backend/API layer — holds the API key, forwards requests to Claude, and streams the response back to the client (usually over Server-Sent Events or a WebSocket).
  3. Conversation store — keeps the message array (user/assistant turns) so context persists across the session, either in memory, a database, or client-side state for short sessions.

Never call the Claude API directly from the browser — this exposes your API key. Always proxy through a backend, even a thin serverless function.

Streaming Responses from Claude

Claude's Messages API supports streaming via server-sent events when you set stream: true. Your backend receives a sequence of events (content_block_delta, message_stop, etc.) and should forward the text deltas to the client as they arrive.

A minimal Node.js proxy pattern looks like this:

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

  const upstream = await fetch("https://api.example.com/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-model",
      messages: req.body.messages,
      stream: true
    })
  });

  for await (const chunk of upstream.body) {
    res.write(chunk); // forward raw SSE chunks
  }
  res.end();
});

The frontend consumes this with EventSource or a fetch + ReadableStream reader, appending each delta to the current assistant message in the UI.

If you're using SubToAPI as your Claude access layer, the same streaming pattern applies against a single, stable endpoint:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet",
    "max_tokens": 1024,
    "stream": true,
    "messages": [
      {"role": "user", "content": "Explain WebSockets in one sentence."}
    ]
  }'

The application key format (sub_live_...) means you can rotate keys per environment or per app without touching your Claude account settings, which matters once you have a staging environment and a production chat app both hitting the same model. Full details on streaming events are in the streaming docs.

Managing Conversation State

Real-time chat isn't just about streaming — it's about maintaining context. Claude's API is stateless: every request must include the full message history you want the model to see. For a chat app this means:

let messages = [];

function sendMessage(userText) {
  messages.push({ role: "user", content: userText });
  streamChatResponse(messages).then((fullReply) => {
    messages.push({ role: "assistant", content: fullReply });
  });
}

If multiple users or sessions share your backend, key the message history by session ID or user ID, and persist it in Redis or a database if the chat needs to survive page reloads or server restarts.

Handling Errors and Reconnects

Real-time connections drop. A production-ready chat app needs:

Where SubToAPI Fits

If you're prototyping or shipping a chat product on top of Claude, the operational overhead — managing API keys, tracking usage across environments, giving teammates scoped access — adds up fast. SubToAPI wraps your existing Claude access in a standard HTTPS API with per-application sub_live_... keys, streaming support, tool use, and usage metadata in one dashboard, so you're not building key rotation and usage tracking yourself before you've even shipped the chat UI. Plans start at €9/month for solo builders, with team and scale tiers for shared workspaces — see pricing or start with the quickstart guide.

Testing Your Real-Time Chat Locally

Before wiring up a full frontend, test the streaming behavior directly with curl to confirm chunks arrive incrementally:

curl -N https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet","max_tokens":256,"stream":true,"messages":[{"role":"user","content":"Count to 5 slowly."}]}'

The -N flag disables curl's output buffering so you see chunks as they arrive, matching what your browser will see. This is the fastest way to debug whether a "stuck" chat UI is a frontend rendering bug or a backend streaming issue.

questions

Do I need WebSockets to stream Claude responses in real time? No. Server-Sent Events (SSE) over a standard HTTP connection are sufficient for one-directional streaming from server to client, which covers most chat UIs. WebSockets add value mainly for bidirectional features like typing indicators or multi-user chat rooms.

How do I keep conversation context across multiple messages? Claude's API is stateless, so you must resend the full message history (or a trimmed/summarized version) with every request. Store the array of user/assistant turns server-side or client-side and append to it after each exchange.

Can I build a real-time Claude chat app without managing raw API keys myself? Yes — using a service like SubToAPI, you get a hosted HTTPS endpoint with application-scoped keys, streaming, and usage tracking already built in, so your chat backend just calls a standard REST endpoint instead of managing Claude credentials directly. Check the messages docs for request/response details.

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 →