In This Tutorial
The Claude chatbot tutorial you're about to follow produces a working, production-ready AI chatbot โ not a toy demo. By the end of this guide, you'll have a chatbot that maintains conversation history, responds with your company's tone, handles errors gracefully, and is ready to deploy behind a real domain. The Claude API is straightforward, but there are architectural decisions you need to get right from the start if this is heading into production.
This tutorial uses Node.js for the backend and vanilla JavaScript for the frontend. If you're a Python shop, we've included Python equivalents for every code block. The patterns are identical โ only the syntax changes. Whether you're building for a SaaS product, an enterprise intranet, or a customer-facing support portal, this architecture scales.
What You'll Build
This Claude chatbot tutorial covers the complete stack: a REST API backend that communicates with Anthropic's Claude API, a lightweight frontend chat UI, conversation history management, a configurable system prompt, and basic error handling. The chatbot will support multi-turn conversations โ meaning Claude will remember what was said earlier in the conversation, not just respond to the last message in isolation.
The finished product is a chatbot that can be embedded into any webpage with a single script tag. You'll also understand how to customise it for your use case โ whether that's customer support, internal knowledge base assistant, or a developer productivity tool. If you want to skip straight to deploying this at scale for your organisation, our Claude API integration service handles the full architecture design, security review, and production deployment.
What You'll Have When Done
- A working Node.js (or Python/Flask) backend connected to the Claude API
- A chat UI that supports multi-turn conversations
- Configurable system prompt for custom chatbot behaviour
- Error handling, rate limit awareness, and cost tracking
- Deployment-ready configuration for Vercel, Railway, or AWS
Prerequisites
Before starting this Claude chatbot tutorial, make sure you have the following: Node.js 18+ (or Python 3.9+) installed on your machine, a free Anthropic account with an active API key (get one at console.anthropic.com), and basic familiarity with REST APIs and JSON. You don't need to know anything about AI or machine learning โ the Claude API handles all of that.
Estimated time: 90โ120 minutes for a complete first-time build. If you've done this before, you can have a working prototype running in under 30 minutes. The Claude API guide has a full breakdown of pricing, models, and rate limits if you want to understand the platform before diving in.
Step 1: Set Up Your Project
Create a new project directory and initialise your Node.js project. We'll use Express for the backend and the official Anthropic SDK for API calls.
Initialise your Node.js project
Create the directory, install dependencies, and set up your folder structure.
# Create and enter project directory
mkdir claude-chatbot && cd claude-chatbot
# Initialise npm project
npm init -y
# Install required packages
npm install express @anthropic-ai/sdk dotenv cors
# Create project structure
mkdir public
touch server.js .env public/index.html public/chat.js
For Python users:
# Create virtual environment
python -m venv venv && source venv/bin/activate # Windows: venv\Scripts\activate
# Install packages
pip install flask anthropic python-dotenv flask-cors
# Create files
touch app.py .env
mkdir -p static templates
Your .env file will store your Anthropic API key โ never commit this to version control. Add .env to your .gitignore immediately.
# .env
ANTHROPIC_API_KEY=sk-ant-your-key-here
PORT=3000
MAX_TOKENS=1024
MODEL=claude-opus-4-6
Step 2: Connect to the Claude API
The Anthropic SDK makes connecting to Claude straightforward. The Claude API uses a messages format where you pass an array of messages โ each with a role of either user or assistant โ and Claude responds to the full conversation, not just the latest message. This is what enables multi-turn conversations.
Initialise the Anthropic client
Set up the client and test the connection with a simple API call.
// test-connection.js โ run this first to verify your key works
require('dotenv').config();
const Anthropic = require('@anthropic-ai/sdk');
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
async function testConnection() {
const message = await client.messages.create({
model: 'claude-opus-4-6',
max_tokens: 100,
messages: [{ role: 'user', content: 'Say "Connection successful" and nothing else.' }]
});
console.log(message.content[0].text);
}
testConnection().catch(console.error);
Run node test-connection.js. You should see "Connection successful". If you get a 401 error, your API key is wrong or hasn't been activated. If you get a 429, you've hit a rate limit โ which on a new account means you need to add billing to Anthropic Console. Once the connection works, move to the full backend.
Step 3: Build the Backend
The backend has one primary job: accept chat messages from the frontend, add them to the conversation history, send the full history to Claude, and return Claude's response. It also needs to validate inputs, handle errors, and manage conversation sessions.
Build the Express server with conversation management
This is the core of your Claude chatbot.
// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const Anthropic = require('@anthropic-ai/sdk');
const app = express();
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// In-memory session store (use Redis in production)
const sessions = new Map();
const SYSTEM_PROMPT = `You are a helpful assistant for [Your Company Name].
You help users with questions about our products and services.
Be concise, professional, and accurate. If you don't know something, say so.
Do not make up information about our products.`;
app.post('/api/chat', async (req, res) => {
const { message, sessionId } = req.body;
// Validate input
if (!message || typeof message !== 'string' || message.length > 10000) {
return res.status(400).json({ error: 'Invalid message' });
}
// Get or create session
if (!sessions.has(sessionId)) {
sessions.set(sessionId, []);
}
const history = sessions.get(sessionId);
// Add user message to history
history.push({ role: 'user', content: message });
try {
const response = await client.messages.create({
model: process.env.MODEL || 'claude-opus-4-6',
max_tokens: parseInt(process.env.MAX_TOKENS) || 1024,
system: SYSTEM_PROMPT,
messages: history
});
const assistantMessage = response.content[0].text;
// Add assistant response to history
history.push({ role: 'assistant', content: assistantMessage });
// Limit history to last 20 messages to control costs
if (history.length > 20) {
sessions.set(sessionId, history.slice(-20));
}
res.json({
response: assistantMessage,
usage: response.usage,
sessionId
});
} catch (error) {
console.error('Claude API error:', error);
if (error.status === 429) {
return res.status(429).json({ error: 'Rate limit reached. Please try again shortly.' });
}
res.status(500).json({ error: 'Failed to get response from Claude.' });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Building this for enterprise use?
The architecture above works for a prototype. Production deployments for 1,000+ users require Redis-backed sessions, rate limiting per user, request logging for compliance, SSO integration, and cost allocation tracking. Our Claude API integration service handles all of this โ including security review and governance configuration.
Talk to a Claude Architect โStep 4: Build the Chat Interface
The frontend is a clean, functional chat UI. We're using vanilla JavaScript โ no React, no framework dependencies โ so it embeds anywhere. The key behaviours are: send message on Enter, show a typing indicator while Claude responds, scroll to the latest message automatically, and generate a session ID that persists for the browser tab.
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude Chatbot</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.chat-container { width: 100%; max-width: 640px; height: 600px; background: #fff; border-radius: 16px; box-shadow: 0 8px 40px rgba(0,0,0,0.1); display: flex; flex-direction: column; overflow: hidden; }
.chat-header { background: #1a1a2e; color: white; padding: 20px 24px; font-size: 1rem; font-weight: 600; }
.chat-messages { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; }
.message { max-width: 80%; padding: 12px 16px; border-radius: 12px; font-size: 0.9rem; line-height: 1.6; }
.message.user { background: #e8723a; color: white; align-self: flex-end; border-radius: 12px 12px 2px 12px; }
.message.assistant { background: #f0f0f0; color: #333; align-self: flex-start; border-radius: 12px 12px 12px 2px; }
.message.typing { background: #f0f0f0; color: #999; }
.chat-input { border-top: 1px solid #eee; padding: 16px 20px; display: flex; gap: 10px; }
.chat-input input { flex: 1; border: 1px solid #ddd; border-radius: 8px; padding: 10px 16px; font-size: 0.9rem; outline: none; }
.chat-input input:focus { border-color: #e8723a; }
.chat-input button { background: #e8723a; color: white; border: none; border-radius: 8px; padding: 10px 20px; font-weight: 600; cursor: pointer; font-size: 0.9rem; }
.chat-input button:hover { background: #d4612a; }
.chat-input button:disabled { background: #ccc; cursor: not-allowed; }
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">๐ฌ Ask me anything</div>
<div class="chat-messages" id="messages"></div>
<div class="chat-input">
<input type="text" id="userInput" placeholder="Type your message..." maxlength="2000">
<button id="sendBtn" onclick="sendMessage()">Send</button>
</div>
</div>
<script src="chat.js"></script>
</body>
</html>
// public/chat.js
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const messagesEl = document.getElementById('messages');
const inputEl = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
function appendMessage(role, text) {
const div = document.createElement('div');
div.className = `message ${role}`;
div.textContent = text;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
return div;
}
async function sendMessage() {
const message = inputEl.value.trim();
if (!message) return;
sendBtn.disabled = true;
inputEl.value = '';
appendMessage('user', message);
const typingEl = appendMessage('typing assistant', '...');
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, sessionId })
});
const data = await res.json();
typingEl.remove();
if (res.ok) {
appendMessage('assistant', data.response);
} else {
appendMessage('assistant', data.error || 'Something went wrong. Please try again.');
}
} catch (err) {
typingEl.remove();
appendMessage('assistant', 'Connection error. Please check your internet and try again.');
} finally {
sendBtn.disabled = false;
inputEl.focus();
}
}
Step 5: Add a System Prompt
The system prompt is the most important configuration in your Claude chatbot. It defines the persona, scope, and constraints of your chatbot. A weak system prompt produces a generic chatbot. A well-crafted system prompt produces a specialist assistant that stays on-task, sounds like your brand, and knows when to escalate to a human.
The system prompt runs before every conversation and is not visible to users. Claude treats it as authoritative instructions. Here's a production-quality system prompt template you can adapt:
const SYSTEM_PROMPT = `You are [Company Name]'s AI assistant, specialising in [domain].
ROLE:
Help users with [specific tasks]. You are knowledgeable, concise, and professional.
SCOPE:
- Answer questions about [topic 1], [topic 2], and [topic 3]
- Do not discuss competitors by name
- Do not provide pricing unless the user asks directly
- For complex account issues, direct users to [support email]
TONE:
- Professional but approachable
- Use plain language, not technical jargon
- Keep responses under 200 words unless asked for detail
LIMITS:
- If you don't know something, say "I'm not sure, but you can contact [support email] for help"
- Do not make promises about features or timelines
- Do not discuss internal company information
Today's date: ${new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
`;
Need help writing a system prompt that works for your use case? Read our guide on Claude prompt engineering for enterprise or book a consultation with our architects.
Step 6: Handle Conversation History
Conversation history is what separates a chatbot from a search box. Claude processes the entire message history on every call, which means costs grow with each exchange. You need a strategy for history management before you deploy to production.
Three approaches to history management
The simplest approach โ which we implemented in Step 3 โ is a sliding window that keeps the last N messages. This works for most support and FAQ chatbots where conversations are short. A more sophisticated approach uses Claude's prompt caching feature to cache the system prompt and earlier conversation segments, cutting token costs by up to 90% on long conversations. The third approach, semantic summarisation, uses Claude itself to periodically compress older conversation turns into a summary, preserving context while reducing token count.
For a production chatbot serving thousands of users daily, you should also implement Claude prompt caching. It reduces latency and cost dramatically on conversations that share a long system prompt. Our API integration service includes prompt caching architecture as standard.
// Enhanced history management with cost tracking
function manageHistory(history, newMessage, maxMessages = 20) {
history.push(newMessage);
// Sliding window
if (history.length > maxMessages) {
// Keep first exchange for context, then last (maxMessages - 2) messages
return [history[0], history[1], ...history.slice(-(maxMessages - 2))];
}
return history;
}
// Track costs per session
function calculateCost(usage, model = 'claude-opus-4-6') {
const rates = {
'claude-opus-4-6': { input: 15, output: 75 }, // per million tokens
'claude-sonnet-4-6': { input: 3, output: 15 },
'claude-haiku-4-5-20251001': { input: 0.25, output: 1.25 }
};
const rate = rates[model] || rates['claude-opus-4-6'];
return {
inputCost: (usage.input_tokens / 1_000_000) * rate.input,
outputCost: (usage.output_tokens / 1_000_000) * rate.output
};
}
Step 7: Deploy to Production
For hobby projects and small deployments, Vercel or Railway work well โ both support Node.js, both handle environment variables cleanly, and both have free tiers. For enterprise deployments, you'll want to deploy to your existing cloud infrastructure (AWS, Azure, or GCP) with your security and compliance requirements taken into account.
Deploy to Railway (fastest path)
Push to GitHub, connect to Railway, add your ANTHROPIC_API_KEY environment variable. Done.
# Install Railway CLI
npm install -g @railway/cli
# Login and initialise
railway login
railway init
# Add environment variables
railway variables set ANTHROPIC_API_KEY=sk-ant-your-key-here
railway variables set MODEL=claude-opus-4-6
railway variables set MAX_TOKENS=1024
# Deploy
railway up
For AWS Lambda or Azure Functions deployment, you'll need to handle the stateless nature of serverless โ session history needs to live in a database like DynamoDB or Cosmos DB, not in memory. This is covered in detail in our Claude API enterprise guide.
Enterprise Considerations
This Claude chatbot tutorial gets you to a working prototype. Deploying this to 10,000 users across an enterprise requires several additional layers of engineering that most tutorials skip.
Authentication and access control
Every API call to Claude costs money and produces a response. Without authentication, anyone who finds your endpoint can use your API quota. At minimum, implement a token-based auth system. For enterprise intranet chatbots, integrate with your identity provider (Okta, Azure AD, Google Workspace) so only authenticated employees can access the chatbot. Our Claude security and governance service covers the full enterprise access control architecture.
Rate limiting and cost controls
Without rate limits, a single user can send thousands of messages and exhaust your monthly API budget. Implement per-user rate limits (e.g. 50 messages per hour), daily spend alerts on your Anthropic Console, and consider model selection logic that routes simple questions to Claude Haiku and only sends complex queries to Sonnet or Opus.
Audit logging and compliance
If your chatbot handles sensitive data โ customer information, financial queries, medical questions โ you need audit logs. Every message in, every message out, timestamps, user identifiers, and session IDs. This is a legal requirement in many regulated industries. See our guide on Claude for regulated industries for the full compliance architecture.
Model selection strategy
Claude Opus 4.6 is the most capable but most expensive model. Claude Haiku 4.5 is 60x cheaper and handles most FAQ-style queries perfectly well. A production chatbot should classify incoming messages by complexity and route them to the appropriate model. Simple greetings, FAQs, and lookup questions go to Haiku. Complex reasoning, multi-step analysis, and sensitive queries go to Sonnet or Opus. This alone can reduce your API costs by 70โ80%. See the Claude model comparison guide for how to implement this routing.
Key Takeaways
- The Claude API uses a messages array โ always pass the full conversation history, not just the last message
- System prompts define chatbot behaviour โ invest time here for best results
- Manage conversation history with a sliding window to control costs
- Use different Claude models for different query types to optimise cost vs capability
- Production deployments need auth, rate limiting, and audit logs โ not optional for enterprise use