# Quick Start

You want a streaming chat in your app. TanStack AI streams from a server route. The hook for your framework renders the tokens.

> [!TIP]
> If you do not want a key per provider, [OpenRouter](../adapters/openrouter) gives you 300+ models with one API key.

React Native or Expo needs an absolute server URL and an XHR transport. See [Quick Start: React Native](./quick-start-react-native).

No UI: see [Quick Start: Server Only](./quick-start-server).

## 1. Install

```sh
npm i @tanstack/ai @tanstack/ai-angular @tanstack/ai-openai
```

## 2. Stream from the server

Call `chat()`. Then wrap the result with `toServerSentEventsResponse`.

```typescript
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-5.6"),
    messages,
    threadId,
    runId,
  });

  return toServerSentEventsResponse(stream);
}
```

This works with TanStack Start, Next.js, SvelteKit, Hono, and any host that returns a Web `Response`.

If your server is Node streams (Express), see [Quick Start: Server Only](./quick-start-server).

Put the API key on the server:

```bash
OPENAI_API_KEY=your-openai-api-key
```

The adapter reads `OPENAI_API_KEY` at runtime. Do not send this key to the browser.

If you do not want a server key, see [Bring Your Own Key](../advanced/byok).

## 3. Render the chat

Call `injectChat` in a field initializer. State is a `Signal`. Read it by calling the function.

```typescript ignore
import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { injectChat, fetchServerSentEvents } from "@tanstack/ai-angular";

@Component({
  selector: "app-chat",
  standalone: true,
  imports: [FormsModule],
  template: `
    <div>
      @for (message of chat.messages(); track message.id) {
        <div>
          @for (part of message.parts; track $index) {
            @if (part.type === "text") {
              <p>{{ part.content }}</p>
            }
          }
        </div>
      }
      <form (submit)="send($event)">
        <input [(ngModel)]="draft" name="draft" [disabled]="chat.isLoading()" />
        @if (chat.isLoading()) {
          <button type="button" (click)="chat.stop()">Stop</button>
        } @else {
          <button type="submit" [disabled]="!draft.trim()">Send</button>
        }
      </form>
    </div>
  `,
})
export class ChatComponent {
  chat = injectChat({
    connection: fetchServerSentEvents("/api/chat"),
  });

  draft = "";

  send(event: Event) {
    event.preventDefault();
    const text = this.draft.trim();
    if (text && !this.chat.isLoading()) {
      void this.chat.sendMessage(text);
      this.draft = "";
    }
  }
}
```

Call `injectChat` in a field initializer or the constructor. A call in `ngOnInit` throws.

`injectChat` subscribes to `DestroyRef`. In-flight requests stop when the component is destroyed.

See the [Angular API](../api/ai-angular).

Send a message. Tokens show up in the UI.

## Later

- [Tools](../tools/tools) for function calling
- [Streaming](../chat/streaming) for cancel, callbacks, and transports
- [Adapters](../adapters/openai) for other providers
