Tutorials12 min read

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 dotenv

Create 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.env

If 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);

What each parameter does:
ParameterRequiredDescription
modelYesWhich Claude model to use. claude-sonnet-4-6 is the best balance of speed and capability.
max_tokensYesMaximum tokens in Claude's reply. 1024 is a safe default for most tasks.
messagesYesThe conversation array. Each message needs role and content.
Available models (May 2026):
ModelBest ForSpeed
claude-opus-4-6Complex reasoning, architecture decisionsSlower
claude-sonnet-4-6Most tasks — sweet spotFast
claude-haiku-4-5-20251001Simple tasks, high volumeFastest

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);
}

Why streaming matters for production apps: Users see output instantly rather than waiting 3–10 seconds for a full response. Streaming is especially important for longer tasks like code generation, document analysis, or report writing.

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?"));

Important: As history grows, so does your token usage (and cost). In production, implement a history trimming strategy — keep the last N turns, or summarize old turns into a condensed context block.

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?");

When to use prompt caching:
  • 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:

Get certified: The Claude Certified Architect (CCA-F) exam validates your ability to design and build production AI systems with Claude. If you can implement everything in this tutorial, you're 60% of the way there. Explore our CCA study guide and practice test bank →

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.