Use Cases (Updated: 6/2/2026)

How to Develop a Chatbot with Claude Code

Learn how to develop a chatbot using Claude Code. Includes practical code examples and step-by-step guidance.

How to Develop a Chatbot with Claude Code

Developing a Chatbot With Claude Code

Building an AI chatbot involves a lot of moving parts: UI, API integration, conversation management, streaming, and more. With Claude Code, you can pull all of these together into a working chatbot in a short amount of time.

Implementing a Basic Chat UI

> Create a React component for a chatbot.
> Support streaming responses, conversation history display, and an input form.
import { useState, useRef, useEffect } from "react";

interface Message {
  id: string;
  role: "user" | "assistant";
  content: string;
}

export function ChatBot() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  const sendMessage = async () => {
    if (!input.trim() || isLoading) return;

    const userMessage: Message = {
      id: crypto.randomUUID(),
      role: "user",
      content: input,
    };

    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setIsLoading(true);

    const assistantId = crypto.randomUUID();
    setMessages((prev) => [
      ...prev,
      { id: assistantId, role: "assistant", content: "" },
    ]);

    try {
      const response = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          messages: [...messages, userMessage].map(({ role, content }) => ({
            role,
            content,
          })),
        }),
      });

      const reader = response.body!.getReader();
      const decoder = new TextDecoder();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        setMessages((prev) =>
          prev.map((m) =>
            m.id === assistantId
              ? { ...m, content: m.content + chunk }
              : m
          )
        );
      }
    } catch (error) {
      setMessages((prev) =>
        prev.map((m) =>
          m.id === assistantId
            ? { ...m, content: "An error occurred. Please try again." }
            : m
        )
      );
    }

    setIsLoading(false);
  };

  return (
    <div className="flex flex-col h-[600px] border rounded-lg">
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((msg) => (
          <div
            key={msg.id}
            className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
          >
            <div
              className={`max-w-[70%] p-3 rounded-lg ${
                msg.role === "user"
                  ? "bg-blue-600 text-white"
                  : "bg-gray-100 text-gray-900"
              }`}
            >
              {msg.content}
            </div>
          </div>
        ))}
        <div ref={messagesEndRef} />
      </div>

      <div className="border-t p-4 flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && sendMessage()}
          placeholder="Type a message..."
          className="flex-1 p-2 border rounded-lg"
          disabled={isLoading}
        />
        <button
          onClick={sendMessage}
          disabled={isLoading}
          className="px-4 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
        >
          Send
        </button>
      </div>
    </div>
  );
}

A Streaming API Route

An API route that calls the Anthropic API on the backend and streams the response back.

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = await client.messages.stream({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: "You are a helpful, polite assistant. Please answer clearly and concisely.",
    messages,
  });

  const encoder = new TextEncoder();

  const readable = new ReadableStream({
    async start(controller) {
      for await (const event of stream) {
        if (
          event.type === "content_block_delta" &&
          event.delta.type === "text_delta"
        ) {
          controller.enqueue(encoder.encode(event.delta.text));
        }
      }
      controller.close();
    },
  });

  return new Response(readable, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
}

Persisting Conversation History

Save the conversation to a database so the user can resume it later.

import { db } from "@/lib/database";

export async function saveConversation(
  userId: string,
  messages: Message[]
) {
  return db.conversation.upsert({
    where: { id: `${userId}-current` },
    update: {
      messages: JSON.stringify(messages),
      updatedAt: new Date(),
    },
    create: {
      id: `${userId}-current`,
      userId,
      messages: JSON.stringify(messages),
    },
  });
}

export async function loadConversation(userId: string): Promise<Message[]> {
  const conv = await db.conversation.findUnique({
    where: { id: `${userId}-current` },
  });
  return conv ? JSON.parse(conv.messages as string) : [];
}

Adding RAG (Retrieval-Augmented Generation)

If you want a chatbot that answers based on internal documents, a RAG setup is a great fit.

import { searchDocuments } from "@/lib/vector-search";

async function generateRAGResponse(query: string, conversationHistory: Message[]) {
  // Search related documents
  const relevantDocs = await searchDocuments(query, { limit: 5 });
  const context = relevantDocs
    .map((doc) => `---\n${doc.title}\n${doc.content}\n---`)
    .join("\n");

  const systemPrompt = `Use the documents below to answer the question.
If the information is not in the documents, reply with "I couldn't find that information."

${context}`;

  return client.messages.stream({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: systemPrompt,
    messages: conversationHistory,
  });
}

For extending features via an MCP server, see the MCP server guide. For effective prompt design, see 5 tips for better prompts.

Summary

With Claude Code, you can efficiently develop a chatbot that includes a chat UI, streaming API, conversation management, and a RAG setup. An incremental approach where you add features step by step works particularly well.

For details, see the official Claude Code documentation and the Anthropic API reference.

2026 Production Upgrade

A chatbot is a small application that accepts a user’s message, keeps enough context to understand the conversation, and returns the next useful response. In plain terms, it is a conversational front door for support, sales, onboarding, search, or internal operations. The hard part is not drawing the chat window. The hard part is deciding what the bot is allowed to do, where it should admit uncertainty, and how it hands the user to a safer workflow when automation is not enough.

Claude Code is useful for this work because the product touches several layers at once: React UI, backend API routes, streaming, persistence, retrieval, analytics, and deployment checks. A thin demo can be built quickly, but a production chatbot needs a narrower promise. For the first release, choose one job: answer the top support questions, qualify inbound leads, search internal documentation, or collect incident details. A focused bot is easier to test, cheaper to run, and more trustworthy than a general assistant that tries to answer everything.

The monetization angle depends on that focus. A support bot lowers human workload. A sales bot increases qualified consultations. A learning bot guides readers toward a course, template, or training offer. A documentation bot keeps existing users successful so they do not churn. Each of those goals needs a different success metric, so define the metric before you write the final prompts.

Architecture Table

LayerResponsibilityProduction note
React chat UIRender messages, input state, loading state, and retry affordancesStart with predictable state from React hooks; see the React useState docs
API routeKeep API keys server-side and normalize requestsAdd input limits, authentication, and request timeouts before public launch
Streaming responseReturn partial text so users do not stare at a blank screenUse Web Streams carefully; the MDN Streams API guide explains the primitives
Conversation storeResume conversations and audit failuresStore only what you need, redact private data, and define deletion behavior
Retrieval layerSearch product docs, policies, or knowledge base pagesIf retrieval finds weak evidence, the bot should say so instead of inventing
Webhook layerSend qualified events to CRM, ticketing, or SlackPair this with Claude Code webhook implementation
Analytics layerMeasure answer success, escalations, and CTA clicksUse Claude Code analytics implementation to close the loop

For the API contract itself, keep the request shape close to the examples in Claude Code API development: an array of role/content messages, a server-side system instruction, and a narrow response stream. That makes it easier to swap the UI, add mobile clients, or introduce a second model later.

Runnable Streaming Demo

The following file runs without external services. Save it as chatbot-stream-demo.mjs and run node chatbot-stream-demo.mjs. It does not call Claude; it proves the streaming and history mechanics before you attach the real API route.

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const faq = new Map([
  ["password", "Open the account page, choose Reset password, and follow the email link."],
  ["pricing", "The pricing page explains plans. For a custom quote, collect team size and required features."],
  ["refund", "Refund requests should be routed to support with the order id and purchase email."],
]);

const history = [];

function chooseAnswer(question) {
  const normalized = question.toLowerCase();
  for (const [keyword, answer] of faq) {
    if (normalized.includes(keyword)) return answer;
  }
  return "I could not find a safe answer in the FAQ. I will hand this to a human operator.";
}

async function* streamText(text) {
  for (const token of text.split(/(\s+)/)) {
    await new Promise((resolve) => setTimeout(resolve, 15));
    yield encoder.encode(token);
  }
}

async function ask(question) {
  history.push({ role: "user", content: question });
  const answer = chooseAnswer(question);
  process.stdout.write(`\nUser: ${question}\nAssistant: `);

  let fullAnswer = "";
  for await (const chunk of streamText(answer)) {
    const token = decoder.decode(chunk);
    fullAnswer += token;
    process.stdout.write(token);
  }

  history.push({ role: "assistant", content: fullAnswer });
}

await ask("How do I reset my password?");
await ask("Can I see pricing before talking to sales?");

console.log(`\n\nSaved ${history.length} messages.`);

In production, replace chooseAnswer with your Claude API call and keep the rest of the shape: push the user message, stream assistant output, then save the final assistant text. This sequence prevents a common failure where the UI shows partial text but the database stores an empty answer because persistence ran too early.

Real Use Cases

The first use case is lead qualification for a SaaS product. The chatbot answers pricing, security, integration, and procurement questions, then routes high-intent visitors to a consultation form. The goal is not to replace sales. The goal is to remove repetitive first-touch questions so humans spend time on qualified conversations.

The second use case is internal help desk automation. Employees ask about expenses, leave requests, VPN access, device replacement, or onboarding steps. A retrieval layer can cite the exact policy page, while a webhook can open a ticket when the answer requires approval. This is a strong workflow because the content is bounded and the fallback path is clear.

The third use case is a learning or content site. A reader asks whether they should learn API integration, webhooks, or analytics next. The bot can recommend Claude Code API development, Claude Code webhook implementation, or Claude Code analytics implementation, then invite the reader to deeper training. That turns search intent into an owned monetization path without interrupting the article.

The fourth use case is incident intake. Instead of asking a user to fill a long form, the bot gathers the error message, timestamp, browser, account id, and reproduction steps. Once the minimum fields are present, it creates a support ticket. Here, success means a complete handoff, not an instant answer.

Pitfalls and Failure Modes

Do not send unlimited conversation history to the model. Long histories increase cost, slow the response, and can let an old instruction override the current task. Summarize older turns, keep the latest decision points, and avoid storing sensitive data that is not needed for support or analytics.

Do not let retrieval failures become confident answers. If the knowledge base search returns weak matches, the bot should say it cannot verify the answer and offer a human path. This one rule prevents many hallucination-driven support problems.

Do not treat streaming as a visual flourish. Streaming creates new failure modes: partial answers after a network drop, duplicate sends, abandoned requests, and UI state that never leaves “loading”. Use disabled submit buttons, request cancellation, timeouts, and an explicit retry message.

Do not publish without measurement. Track unresolved questions, escalation rate, CTA clicks, and the first message that led to a consultation or purchase. If you only count total messages, a noisy bot can look successful while it is actually frustrating users.

Monetization CTA

For a reader-facing site, the CTA should match the conversation stage. A beginner might need a checklist or starter template. A team lead might need an implementation review. A company evaluating automation might need a short consultation. ClaudeCodeLab routes that commercial intent through training, where the next step can be a hands-on session rather than another generic article.

The practical content path is: teach the concept here, explain the API in Claude Code API development, connect downstream systems with webhooks, and improve conversion with analytics. That sequence makes the article useful for readers and creates a credible monetization journey.

What I Verified

I verified the runnable demo with Node.js 20 using only standard TextEncoder, TextDecoder, async generators, and top-level await. For a real app, I would test the React state flow first, then connect the API route, then add persistence and analytics. That order keeps debugging small: if streaming breaks, you know whether the bug is in the UI, the route, or the model call.

#Claude Code #chatbot #AI #streaming #RAG
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.