React UI
Server routes
Return React-compatible Anvia streams from chat and completion routes.
React UI components do not call provider SDKs directly. They render state from @anvia/react.
@anvia/react posts to your API app, and that app owns auth, model selection, tools,
credentials, persistence, and streaming.
The examples use a separate frontend app at http://localhost:5173 and API app at
http://localhost:8787. In that setup, the hook endpoint is the full API URL:
const chat = useChat({ endpoint: "http://localhost:8787/api/chat" });
If the frontend and API run on different origins, configure CORS in the API app. With Hono:
import { cors } from "hono/cors";
app.use("/api/*", cors({ origin: "http://localhost:5173" }));
Chat route
The chat route sends the default UIStreamRequest body:
type UIStreamRequest = {
messages: Message[];
stream: true;
metadata?: JsonValue;
};
Read body.messages, not { message }, when the route is used by @anvia/react.
import type { UIStreamRequest } from "@anvia/core";
import { createEventStream } from "@anvia/server";
export async function POST(request: Request) {
const body = (await request.json()) as UIStreamRequest;
return createEventStream(agent.prompt(body.messages).stream(), {
format: "jsonl",
});
}
Completion route
useCompletion(...) uses the same request shape. A completion route can stream one model call:
import { createCompletionStream, type UIStreamRequest } from "@anvia/core";
import { createEventStream } from "@anvia/server";
export async function POST(request: Request) {
const body = (await request.json()) as UIStreamRequest;
return createEventStream(
createCompletionStream(model, {
instructions: "Write concise product copy.",
messages: body.messages,
maxTokens: 500,
}),
{ format: "jsonl" },
);
}
Product-shaped route
Keep product state on the server. Use metadata or a custom request body for client-selected
options, then validate those values before running the agent.
import type { UIStreamRequest } from "@anvia/core";
import { createEventStream } from "@anvia/server";
type ChatMetadata = {
model?: string;
tenantId?: string;
};
export async function POST(request: Request) {
const user = await requireUser(request);
const body = (await request.json()) as UIStreamRequest;
const metadata = (body.metadata ?? {}) as ChatMetadata;
const tenant = await requireTenant(user, metadata.tenantId);
const agent = createSupportAgent({
model: chooseAllowedModel(user, metadata.model),
tenant,
});
return createEventStream(agent.prompt(body.messages).stream(), {
format: "jsonl",
});
}
JSONL and SSE
JSONL is the default and is the simplest pairing with @anvia/react:
return createEventStream(events, { format: "jsonl" });
Use SSE only when your infrastructure or existing clients need text/event-stream:
return createEventStream(events, { format: "sse" });
If the route returns SSE, configure the client with the same format:
const chat = useChat({
endpoint: "http://localhost:8787/api/chat",
format: "sse",
});
Route checklist
- Keep API keys and provider clients on the server.
- Read
body.messagesfromUIStreamRequest. - Return an Anvia stream through
createEventStream(...). - Validate any client-selected model, provider, tenant, or feature flag.
- Do not expose private tool inputs, secrets, trace payloads, or raw provider responses directly to the browser.
