Claude API with JavaScript & Node.js: Complete Tutorial (2026)
Learn how to use the Claude API with JavaScript and Node.js. Build AI apps with streaming, tool use, multi-turn chat, and prompt caching in under 30 minutes.
Claude API with JavaScript & Node.js: Complete Tutorial (2026)
Every week, thousands of JavaScript developers search for a practical, no-fluff guide to integrating the Claude API into their projects. This is that guide.
By the end of this tutorial you'll have a working Node.js app that calls Claude, streams responses, handles multi-turn conversations, and uses tool calling — the four capabilities that cover 90% of real-world Claude integrations.
Prerequisites and Setup
You need Node.js 18+ and an Anthropic API key. Get your key from the Anthropic Console.
bash# Create project and install the SDK
mkdir claude-js-tutorial && cd claude-js-tutorial
npm init -y
npm install @anthropic-ai/sdk dotenvCreate a .env file:
ANTHROPIC_API_KEY=sk-ant-api03-...Create index.js and add the boilerplate:
jsimport Anthropic from "@anthropic-ai/sdk";
import "dotenv/config";
const client = new Anthropic();
// ANTHROPIC_API_KEY is read automatically from process.envIf you're on CommonJS (older Node or no "type": "module" in package.json), use:
jsconst Anthropic = require("@anthropic-ai/sdk");
require("dotenv").config();
const client = new Anthropic();Your First Claude API Call
Let's start with the simplest possible call — a single question and answer.
jsimport Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function askClaude(question) {
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: question }],
});
return message.content[0].text;
}
const answer = await askClaude(
"What are the top 3 JavaScript frameworks in 2026?"
);
console.log(answer);| Parameter | Required | Description |
|---|---|---|
model | Yes | Which Claude model to use. claude-sonnet-4-6 is the best balance of speed and capability. |
max_tokens | Yes | Maximum tokens in Claude's reply. 1024 is a safe default for most tasks. |
messages | Yes | The conversation array. Each message needs role and content. |
| Model | Best For | Speed |
|---|---|---|
claude-opus-4-6 | Complex reasoning, architecture decisions | Slower |
claude-sonnet-4-6 | Most tasks — sweet spot | Fast |
claude-haiku-4-5-20251001 | Simple tasks, high volume | Fastest |
Streaming Responses
Non-streaming calls wait for the entire response before returning anything. For UIs or long outputs, streaming is far better UX.
jsasync function streamClaude(prompt) {
const stream = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 2048,
messages: [{ role: "user", content: prompt }],
stream: true,
});
process.stdout.write("Claude: ");
for await (const event of stream) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
process.stdout.write(event.delta.text);
}
}
console.log("\n--- Done ---");
}
await streamClaude("Explain how async/await works in JavaScript in 3 paragraphs.");The SDK also provides a higher-level helper that collects the final message:
jsasync function streamWithHelper(prompt) {
const response = await client.messages
.stream({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
})
.on("text", (text) => {
process.stdout.write(text);
})
.finalMessage();
console.log("\nTotal input tokens:", response.usage.input_tokens);
console.log("Total output tokens:", response.usage.output_tokens);
}Multi-Turn Conversations
Real applications need conversation memory — each message builds on the previous ones. Claude's API is stateless, so you manage history yourself.
jsclass ClaudeChat {
constructor(systemPrompt = null) {
this.client = new Anthropic();
this.history = [];
this.systemPrompt = systemPrompt;
}
async send(userMessage) {
// Add user message to history
this.history.push({ role: "user", content: userMessage });
const requestParams = {
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: this.history,
};
// Add system prompt if provided
if (this.systemPrompt) {
requestParams.system = this.systemPrompt;
}
const response = await this.client.messages.create(requestParams);
const assistantMessage = response.content[0].text;
// Add Claude's reply to history
this.history.push({ role: "assistant", content: assistantMessage });
return assistantMessage;
}
reset() {
this.history = [];
}
}
// Usage
const chat = new ClaudeChat(
"You are a senior JavaScript developer and code reviewer. Be concise and direct."
);
console.log(await chat.send("How should I structure a large Node.js project?"));
console.log(await chat.send("What about for a Next.js project specifically?"));
console.log(await chat.send("Should I use the App Router or Pages Router?"));System Prompts: Giving Claude a Role
The system parameter is the most powerful way to shape Claude's behavior. It sets the persona, constraints, and output format for the entire conversation.
jsconst SYSTEM_PROMPTS = {
codeReviewer: `You are an expert JavaScript code reviewer with 10+ years of experience.
Your reviews are:
- Concise: no more than 5 key points per review
- Prioritized: security > correctness > performance > style
- Actionable: every issue includes a concrete fix
- Encouraging: acknowledge what's done well
Format your response as:
## Summary
[1-2 sentence overall assessment]
## Issues Found
[Numbered list, highest priority first]
## What's Done Well
[Brief acknowledgment]`,
tutor: `You are a patient JavaScript tutor helping beginners.
Always explain WHY before HOW. Use simple analogies. Never use jargon without explaining it first.`,
documentationWriter: `You are a technical documentation writer.
Output clean Markdown. Start with a one-sentence TL;DR. Use code examples for every concept. Keep it scannable.`,
};
async function reviewCode(code) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 2048,
system: SYSTEM_PROMPTS.codeReviewer,
messages: [
{
role: "user",
content: `Please review this code:\n\n\`\`\`javascript\n${code}\n\`\`\``,
},
],
});
return response.content[0].text;
}Tool Use (Function Calling)
Tool use lets Claude call functions you define — this is how you build agents that interact with external APIs, databases, or your own application logic.
js// Define tools Claude can use
const tools = [
{
name: "get_weather",
description:
"Get current weather for a city. Returns temperature, conditions, and humidity.",
input_schema: {
type: "object",
properties: {
city: {
type: "string",
description: "City name, e.g. 'San Francisco' or 'Mumbai'",
},
units: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit",
},
},
required: ["city"],
},
},
{
name: "search_database",
description: "Search the product database. Returns matching products.",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
limit: {
type: "number",
description: "Max results to return (default: 5)",
},
},
required: ["query"],
},
},
];
// Implement the actual tool functions
async function executeTool(toolName, toolInput) {
switch (toolName) {
case "get_weather":
// Replace with your actual weather API call
return {
city: toolInput.city,
temperature: 22,
units: toolInput.units || "celsius",
conditions: "Partly cloudy",
humidity: "65%",
};
case "search_database":
// Replace with your actual database query
return {
results: [
{ id: 1, name: "Claude API Guide", price: 19.99 },
{ id: 2, name: "AI Certification Prep", price: 14.99 },
],
total: 2,
};
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
// Agentic loop — Claude keeps calling tools until it has the answer
async function runWithTools(userMessage) {
const messages = [{ role: "user", content: userMessage }];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
tools,
messages,
});
// If Claude is done, return the final text
if (response.stop_reason === "end_turn") {
const textBlock = response.content.find((b) => b.type === "text");
return textBlock?.text ?? "Done.";
}
// If Claude wants to use a tool, execute it and loop
if (response.stop_reason === "tool_use") {
// Add Claude's tool request to history
messages.push({ role: "assistant", content: response.content });
// Process all tool calls in this turn
const toolResults = [];
for (const block of response.content) {
if (block.type === "tool_use") {
console.log(`Calling tool: ${block.name}`, block.input);
const result = await executeTool(block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
});
}
}
// Add tool results back to history
messages.push({ role: "user", content: toolResults });
// Loop — Claude will process results and either answer or call more tools
}
}
}
const answer = await runWithTools(
"What's the weather in Mumbai, and do you have any AI courses I might like?"
);
console.log(answer);Prompt Caching: Cut Costs by Up to 90%
If you're sending the same large system prompt or document on every request, prompt caching dramatically reduces costs and latency. Claude caches prompt prefixes that are 1,024+ tokens.
jsimport Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
defaultHeaders: { "anthropic-beta": "prompt-caching-2024-07-31" },
});
// Large document you're repeatedly analyzing
const LARGE_DOCUMENT = `[Your 5,000-word document here...]`;
async function analyzeDocument(question) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [
{
type: "text",
text: "You are a document analyst. Answer questions based only on the provided document.",
cache_control: { type: "ephemeral" }, // Cache this prefix
},
{
type: "text",
text: LARGE_DOCUMENT,
cache_control: { type: "ephemeral" }, // Cache the document too
},
],
messages: [{ role: "user", content: question }],
});
const usage = response.usage;
console.log("Cache read tokens:", usage.cache_read_input_tokens ?? 0);
console.log("Cache write tokens:", usage.cache_creation_input_tokens ?? 0);
return response.content[0].text;
}
// First call — writes the cache (slightly more expensive)
await analyzeDocument("What is the main argument of this document?");
// Subsequent calls — reads from cache (up to 90% cheaper for the cached tokens)
await analyzeDocument("What evidence supports this argument?");
await analyzeDocument("What are the counterarguments mentioned?");- Analyzing the same document multiple times
- RAG systems with fixed context chunks
- Chatbots with long, fixed system prompts (e.g., company knowledge base)
- Any workflow where the same prefix appears in 3+ consecutive requests
Error Handling in Production
Never ship Claude integrations without proper error handling. The Anthropic SDK throws typed errors you can catch specifically:
jsimport Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function safeClaudeCall(prompt, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
return response.content[0].text;
} catch (error) {
if (error instanceof Anthropic.APIError) {
const status = error.status;
if (status === 429) {
// Rate limited — wait with exponential backoff
const waitMs = Math.pow(2, attempt) * 1000;
console.warn(`Rate limited. Waiting ${waitMs}ms before retry ${attempt}/${retries}`);
await new Promise((r) => setTimeout(r, waitMs));
continue;
}
if (status === 529) {
// API overloaded — retry after delay
await new Promise((r) => setTimeout(r, 5000 * attempt));
continue;
}
if (status === 400) {
// Invalid request — don't retry, fix the input
throw new Error(`Invalid request: ${error.message}`);
}
if (status === 401) {
throw new Error("Invalid API key. Check your ANTHROPIC_API_KEY.");
}
if (status >= 500 && attempt < retries) {
// Server error — retry
await new Promise((r) => setTimeout(r, 2000 * attempt));
continue;
}
}
throw error; // Re-throw if not handled above
}
}
throw new Error(`Failed after ${retries} retries`);
}Deploying to Production: Environment Variables Checklist
Before you ship, verify:
bash# ✅ API key is in environment variables, NOT hardcoded
echo $ANTHROPIC_API_KEY
# ✅ Key is in .gitignore (never commit it)
cat .gitignore | grep .env
# ✅ Set a reasonable timeout (default is no timeout)
const client = new Anthropic({ timeout: 30_000 }); // 30 seconds
# ✅ Log token usage to track costs
console.log(response.usage); // { input_tokens: N, output_tokens: M }For Next.js App Router specifically, use Route Handlers for API calls (never expose your API key to the browser):
js// app/api/chat/route.js
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY from server env
export async function POST(request) {
const { message } = await request.json();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: message }],
});
return Response.json({ reply: response.content[0].text });
}Key Takeaways
- Install once:
npm install @anthropic-ai/sdk— the SDK handles auth, retries, and TypeScript types automatically - Stream by default for any UI — users expect immediate feedback, not a 5-second wait
- Manage history yourself — Claude's API is stateless; your app holds the conversation array
- System prompts are powerful — invest time in crafting good ones; they determine 80% of output quality
- Tool use enables agents — the agentic loop pattern (call → tool result → call) unlocks Claude's full potential
- Cache large prefixes — if your system prompt or context is 1,024+ tokens and reused, caching cuts costs dramatically
- Always handle rate limits — exponential backoff on 429s is non-negotiable for production apps
Next Steps
You now have everything you need to ship a production-quality Claude integration in JavaScript. Here's where to go from here:
Practice the concepts:Our Claude Certified Architect (CCA) practice tests include hands-on coding challenges covering the Anthropic API — including tool use, multi-agent patterns, and prompt engineering tested in the real exam.
Go deeper on specific topics:- Claude Multi-Agent Orchestration Tutorial — build systems where multiple Claude instances collaborate
- Claude API Prompt Caching Guide — full breakdown of caching strategies and cost analysis
- How to Build an MCP Server for Claude — extend Claude with custom tools via the Model Context Protocol
Ready to Start Practicing?
300+ scenario-based practice questions covering all 5 CCA domains. Detailed explanations for every answer.
Free CCA Study Kit
Get domain cheat sheets, anti-pattern flashcards, and weekly exam tips. No spam, unsubscribe anytime.