A Chatbot Widget
Combine a backend endpoint, conversation history, and streaming into an assistant widget.
A chatbot widget brings several pieces together: a backend endpoint, stored conversation history, and streaming for a responsive feel.
The parts
- Frontend — a simple chat box that sends messages to your server and appends replies.
- Backend — an endpoint that keeps the conversation history and calls Claude.
- System prompt — defines the assistant's role and scope.
Keep it focused
Give the assistant a clear job in the system prompt ("help users with orders on this store") so it stays on-topic and useful.
Example
// Minimal chatbot endpoint with per-session history and streaming
const sessions = new Map(); // sessionId -> messages[]
app.post("/api/chat", async (req, res) => {
const { sessionId, message } = req.body;
const history = sessions.get(sessionId) ?? [];
history.push({ role: "user", content: message });
res.setHeader("Content-Type", "text/plain; charset=utf-8");
const stream = client.messages.stream({
model: "claude-opus-4-8", max_tokens: 1024,
system: "You are a support assistant for an online store. Be concise.",
messages: history,
});
stream.on("text", (t) => res.write(t));
const final = await stream.finalMessage();
history.push({ role: "assistant", content: final.content });
sessions.set(sessionId, history);
res.end();
});When to use it
- Add a floating chatbot widget to your SaaS dashboard that streams Claude answers and remembers the session history.
- Build a website assistant that uses a system prompt scoped to your product docs and streaming for responsiveness.
- Embed a support bot in your React app that stores conversation history in useState and clears it on page refresh.
More examples
Backend endpoint for a chat message
Accepts the full messages array from the client so history is maintained by the browser and replayed each turn.
// POST /api/chat — receives messages array, returns Claude's reply
export async function POST(req) {
const { messages } = await req.json();
const msg = await client.messages.create({
model: "claude-haiku-4-5",
max_tokens: 512,
system: "You are a helpful assistant for AcmeCorp customers.",
messages,
});
return Response.json({ reply: msg.content[0].text });
}React state for conversation history
Keeps conversation history in React state, appending user and assistant turns before each API call.
const [messages, setMessages] = useState([]);
async function sendMessage(text) {
const next = [...messages, { role: "user", content: text }];
setMessages(next);
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: next }),
});
const { reply } = await res.json();
setMessages(m => [...m, { role: "assistant", content: reply }]);
}Stream replies into the chat bubble
Streams tokens into the last message bubble by updating state incrementally as each chunk arrives.
async function sendStreamingMessage(text) {
const next = [...messages, { role: "user", content: text }];
setMessages([...next, { role: "assistant", content: "" }]);
const res = await fetch("/api/chat-stream", {
method: "POST",
body: JSON.stringify({ messages: next }),
headers: { "Content-Type": "application/json" },
});
const reader = res.body.getReader();
const dec = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
setMessages(m => [
...m.slice(0, -1),
{ role: "assistant", content: m.at(-1).content + dec.decode(value) },
]);
}
}
Discussion