How to Run the Claude API in Node.js (2026 Step-by-Step Guide)

Want to call Claude from your own Node.js app? This guide walks you through it end to end — installing the official SDK, making your first request, streaming responses, holding a multi-turn conversation, handling errors, and keeping your API key safe. Every code sample below is current for the 2026 SDK, uses plain JavaScript, and runs on Node.js 20 or newer.

By the end, you’ll have a working script that talks to Claude and a clear mental model of how the Messages API behaves, so you can drop it into an Express route, a background worker, or a CLI tool without guesswork.

What you need before you start

  • Node.js 20 LTS or newer. Check with node --version. The official SDK dropped support for older, end-of-life Node versions, so anything below 20 will give you trouble.
  • An Anthropic API key. Create a developer account at the Anthropic Console and generate a key. Keys look like sk-ant-....
  • A few dollars of credit (or free-tier allowance) on your Anthropic account. API usage is pay-as-you-go — more on cost near the end.

That’s it. No framework required. If you can run a .js file, you can call Claude.

Step 1: Set up your project

Create a folder, initialise it, and install the official SDK. We’ll also add dotenv to load your API key from a file during local development.

mkdir claude-node-demo
cd claude-node-demo
npm init -y
npm install @anthropic-ai/sdk dotenv

Open package.json and add "type": "module" so you can use modern import syntax. Your file should look roughly like this:

{
  "name": "claude-node-demo",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "@anthropic-ai/sdk": "^0.60.0",
    "dotenv": "^16.4.5"
  }
}

Step 2: Store your API key safely

Never hardcode your key into your source files, and never commit it to Git. Store it as an environment variable instead. For local work, create a file named .env in your project root:

ANTHROPIC_API_KEY=sk-ant-your-actual-key-here

Then add .env to your .gitignore immediately so it never reaches a public repository:

echo ".env" >> .gitignore
echo "node_modules" >> .gitignore

The SDK automatically reads the ANTHROPIC_API_KEY environment variable, so you never have to pass the key manually in your code. That’s one less place for it to leak.

Step 3: Make your first API call

Create a file called index.js and paste in the following. This is the complete “hello world” for the Claude API.

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

// The client reads ANTHROPIC_API_KEY from your environment automatically.
const client = new Anthropic();

async function main() {
  const message = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    messages: [
      { role: "user", content: "Explain what an API is in one paragraph." },
    ],
  });

  console.log(message.content[0].text);
}

main();

Run it:

node index.js

You should see Claude’s answer printed to your terminal. A few things worth understanding here:

  • model selects which Claude model answers. We’re using Sonnet, the balanced everyday choice (more on picking a model below).
  • max_tokens is required — it caps the length of the response, not your prompt. Set it comfortably above what you expect the answer to need.
  • messages is an array of turns. Each turn has a role (user or assistant) and content.
  • The reply comes back as message.content, an array of blocks. For plain text, message.content[0].text is what you want.

Step 4: Add a system prompt

A system prompt sets Claude’s role and behaviour for the whole conversation. It’s passed as a top-level system field, separate from the message list.

const message = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  system: "You are a senior Node.js engineer. Answer concisely with code examples.",
  messages: [
    { role: "user", content: "How do I read a file asynchronously?" },
  ],
});

console.log(message.content[0].text);

Use the system prompt for tone, constraints, output format, and any persistent context — it’s the cleanest way to steer responses without repeating instructions on every turn.

Step 5: Stream responses for a better UX

For anything longer than a sentence or two, waiting for the full response feels slow. Streaming prints each chunk of text as Claude generates it — the same effect you see in the Claude chat interface.

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

const client = new Anthropic();

async function streamExample() {
  const stream = client.messages.stream({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    messages: [
      { role: "user", content: "Write a short story about a robot learning to cook." },
    ],
  });

  // Print each token as it arrives.
  stream.on("text", (text) => {
    process.stdout.write(text);
  });

  const finalMessage = await stream.finalMessage();
  console.log("\n\nTokens used:", finalMessage.usage);
}

streamExample();

The .stream() helper gives you convenient event handlers like on("text", ...) and accumulates the full message for you when it finishes. If you’d rather manage memory tightly and iterate the raw events yourself, you can use client.messages.create({ ..., stream: true }) and loop over the async iterable instead.

Step 6: Hold a multi-turn conversation

Claude keeps no memory between API calls. Each request is stateless — if you want a back-and-forth, you send the full conversation history every time. The pattern is simple: keep an array, push each new user message and each assistant reply onto it, and resend the whole thing.

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

const client = new Anthropic();
const history = [];

async function chat(userMessage) {
  history.push({ role: "user", content: userMessage });

  const response = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    system: "You are a helpful coding assistant.",
    messages: history,
  });

  const reply = response.content[0].text;
  history.push({ role: "assistant", content: reply });
  return reply;
}

console.log(await chat("What is a closure in JavaScript?"));
console.log(await chat("Now show me one practical example."));

Because the second question references “one practical example” without repeating the topic, Claude only answers it correctly because the closure question and its answer are still in history. Drop the history and the context is gone.

One thing to watch: every resent turn costs input tokens. A long conversation re-sends everything on each call, so cost grows as the thread grows. For long sessions, consider trimming old turns or summarising them.

Step 7: Handle errors like production code

Network calls fail, rate limits trigger, and keys expire. The SDK exports typed error classes so you can respond to each case sensibly instead of catching everything the same way.

import "dotenv/config";
import Anthropic, {
  APIError,
  RateLimitError,
  APIConnectionError,
} from "@anthropic-ai/sdk";

const client = new Anthropic();

async function safeCreate(content) {
  try {
    const response = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 512,
      messages: [{ role: "user", content }],
    });
    return response.content[0].text;
  } catch (err) {
    if (err instanceof RateLimitError) {
      console.error("Rate limited — back off and retry later.");
    } else if (err instanceof APIConnectionError) {
      console.error("Network problem:", err.message);
    } else if (err instanceof APIError) {
      console.error(`API error ${err.status}:`, err.message);
    } else {
      console.error("Unexpected error:", err);
    }
    return null;
  }
}

console.log(await safeCreate("Give me one Node.js performance tip."));

A common first-run error is 401 authentication_error. That almost always means your key is missing, misspelled, or has stray whitespace. Confirm ANTHROPIC_API_KEY is set and that your .env file is in the folder you’re running from.

Which Claude model should you use?

Anthropic’s current lineup splits into three everyday tiers. Pricing below is per million tokens (input / output) at standard rates, accurate as of mid-2026 — always confirm current numbers on the official pricing page before you rely on them.

ModelBest forPrice (in / out per Mtok)
Claude Haiku 4.5Fast, cheap, high-volume tasks: classification, tagging, simple generation$1 / $5
Claude Sonnet 4.6The balanced default for most production work$3 / $15
Claude Opus 4.8Hardest reasoning, complex coding, deep analysis$5 / $25

The practical rule most teams follow: start on Sonnet 4.6, drop to Haiku 4.5 for simple high-volume jobs, and reach for Opus 4.8 only when a task genuinely needs its extra reasoning depth. Output tokens cost far more than input tokens across every tier, so controlling response length is usually your biggest cost lever.

Two cost-savers worth knowing early: prompt caching gives up to a 90% discount on repeated context (like a fixed system prompt), and the Batch API gives a 50% discount on non-urgent, asynchronous workloads.

Wiring Claude into an Express route

Most real usage happens on a server, not in a terminal. Here’s the minimal pattern for exposing Claude through an Express endpoint. Note that the client is created once and reused — it manages connection pooling internally, so creating a new client per request just wastes resources.

import "dotenv/config";
import express from "express";
import Anthropic from "@anthropic-ai/sdk";

const app = express();
app.use(express.json());

const client = new Anthropic(); // created once, reused across requests

app.post("/ask", async (req, res) => {
  try {
    const response = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      messages: [{ role: "user", content: req.body.question }],
    });
    res.json({ answer: response.content[0].text });
  } catch (err) {
    res.status(500).json({ error: "Something went wrong" });
  }
});

app.listen(3000, () => console.log("Listening on http://localhost:3000"));

Send it a request and you have a working AI endpoint:

curl -X POST http://localhost:3000/ask \
  -H "Content-Type: application/json" \
  -d '{"question": "What is Node.js in one sentence?"}'

Production tips before you ship

  • Reuse one client instance across your whole app. It pools connections for you.
  • Set request timeouts. Use the SDK’s timeout option so a hanging request can’t block forever — 30 seconds for short replies, longer for big generations.
  • Log token usage. Track input_tokens and output_tokens from every response’s usage field so cost never surprises you.
  • Pin your SDK major version in package.json. The SDK follows semver; pinning avoids surprise breaking changes in production.
  • Keep the key server-side only. Never ship your API key to a browser or mobile client. Proxy every call through your backend.

Wrapping up

You now have everything needed to run the Claude API in Node.js: a safely stored key, a first request, streaming, multi-turn conversations, proper error handling, and a server-ready Express pattern. The mental model is small — stateless requests, you own the history, and content[0].text is your answer — but it scales cleanly from a throwaway script to a production service.

From here, two natural next steps are tool use (letting Claude call your functions) and the Model Context Protocol (MCP) for connecting Claude to external systems. Both build directly on the Messages API you just learned.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top