Skip to content
Getting Started

Quick Start

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?

1. Install

shell
npm i @tanstack/ai @tanstack/ai-react @tanstack/ai-openai

Pick 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.

2. Stream from the server

Put your API key in the server environment, for example in .env:

shell
OPENAI_API_KEY=your-openai-api-key

Create a route at /api/chat. In Next.js, that is app/api/chat/route.ts. Call chat(), then return the stream with toServerSentEventsResponse:

ts
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.

3. Render the chat

Call useChat from @tanstack/ai-react. Hold the composer text in useState. Pass it to sendMessage.

tsx
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.

4. Try it

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.

How it works

  1. sendMessage posts the messages to /api/chat.
  2. chatParamsFromRequest reads the messages and the run ids from the request.
  3. chat() calls the model through the adapter and returns a stream of events.
  4. toServerSentEventsResponse sends those events back as Server-Sent Events.
  5. fetchServerSentEvents reads the events, and the hook adds each chunk to messages.

Each message has parts. This page renders only the text parts. Tool calls and other content arrive as other part types.

Next

Add one feature at a time:

  • Tools: let the model call your functions.
  • Streaming: cancel, callbacks, and transports.
  • Adapters: switch to another provider.

Or build a full app in a tutorial, such as Basic Chat or Generate Image.

Advanced

Hosts

The server route returns a Web Response. It works with any host that can return one:

  • TanStack Start
  • Next.js
  • SvelteKit
  • Hono
  • A Remix controller action

If your server uses Node streams (Express), see Quick Start: Server Only.

Bad request bodies

If the request body is not valid, chatParamsFromRequest throws a Response with status 400.

  • TanStack Start, SolidStart, Remix, and React Router 7 return a thrown Response to the client for you.
  • Next.js Route Handlers, SvelteKit, Hono, and raw Node do not. Wrap the call in try/catch and return the caught Response.

Keys in the browser

If you do not want a key on the server, users can paste their own provider key. See Bring Your Own Key.