This guide adds a chat that renders tokens while the model writes them. You add a server route, an API key, and a UI.
TanStack AI gives you each piece. chat() streams from your server route. A hook for your framework renders the tokens. This takes about 5 minutes.
Not on the web?
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-openaipnpm add @tanstack/ai @tanstack/ai-react @tanstack/ai-openaiyarn add @tanstack/ai @tanstack/ai-react @tanstack/ai-openaibun add @tanstack/ai @tanstack/ai-react @tanstack/ai-openaiPick your framework at the top of the page. The install command and the UI code follow your pick.
Tip
If you do not want a key for each provider, use OpenRouter. It gives you 300+ models with one API key.
Put your API key in the server environment, for example in .env:
OPENAI_API_KEY=your-openai-api-keyCreate a route at /api/chat. In Next.js, that is app/api/chat/route.ts. Call chat(), then return the stream with toServerSentEventsResponse:
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: openaiText("gpt-6.1-sol"),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}The adapter reads OPENAI_API_KEY at runtime. Do not send this key to the browser.
Call useChat from @tanstack/ai-react. Hold the composer text in useState. Pass it to sendMessage.
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, isLoading, stop } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return (
<>
{messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) =>
part.type === "text" ? <p key={index}>{part.content}</p> : null,
)}
</div>
))}
<form
onSubmit={(event) => {
event.preventDefault();
if (input.trim() === "") {
return;
}
sendMessage(input);
setInput("");
}}
>
<input
value={input}
onChange={(event) => setInput(event.target.value)}
/>
{isLoading ? (
<button type="button" onClick={stop}>
Stop
</button>
) : (
<button type="submit">Send</button>
)}
</form>
</>
);
}messages updates as chunks arrive. isLoading is true while the run is in flight.
See the React API.
No framework? Use ChatClient from @tanstack/ai-client. See the client API.
Start your app and send a message. The answer appears word by word while the model writes it. Click Stop to cancel a run.
You now have a streaming chat.
Each message has parts. This page renders only the text parts. Tool calls and other content arrive as other part types.
Add one feature at a time:
Or build a full app in a tutorial, such as Basic Chat or Generate Image.
The server route returns a Web Response. It works with any host that can return one:
If your server uses Node streams (Express), see Quick Start: Server Only.
If the request body is not valid, chatParamsFromRequest throws a Response with status 400.
If you do not want a key on the server, users can paste their own provider key. See Bring Your Own Key.