Claude API Node.js Integration Tutorial (2024 Guide)
Claude API Node.js Integration Tutorial
If you're building a Node.js app and want to call Claude, you need three things: an API key, the official SDK (or plain fetch), and a request shaped correctly for the Messages API. This tutorial walks through all of it — from npm install to a working chat endpoint with streaming — using code you can copy directly into a project.
By the end you'll have a small Express route that accepts a user message, sends it to Claude, and returns a response, plus the streaming variant for real-time UIs. We'll also cover the two ways to authenticate: directly with Anthropic, or through a proxy like SubToAPI if you want a single HTTPS API key shared across a team instead of managing raw provider credentials.
Step 1: Install the SDK
Anthropic publishes an official Node.js SDK. Install it in your project:
npm install @anthropic-ai/sdk
If you'd rather avoid an SDK dependency, plain fetch works fine too since the API is just JSON over HTTPS — we'll show both.
Step 2: Set up your API key
Store your key as an environment variable, never hardcoded:
# .env
ANTHROPIC_API_KEY=sk-ant-...
Load it with dotenv or your framework's config system. If you're using SubToAPI instead of a raw Anthropic key, the variable would be SUBTOAPI_KEY=sub_live_... — same pattern, different provider.
Step 3: Send your first message
Here's a minimal script using the official SDK:
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
async function main() {
const message = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [
{ role: "user", content: "Explain event loops in Node.js in two sentences." },
],
});
console.log(message.content[0].text);
}
main();
Run it with node index.js (make sure your package.json has "type": "module" or use require syntax instead). You should get a text response printed to your console within a second or two.
Step 4: Wire it into an Express route
Most integrations aren't standalone scripts — they're API endpoints. Here's a basic Express route:
import express from "express";
import Anthropic from "@anthropic-ai/sdk";
const app = express();
app.use(express.json());
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
app.post("/api/chat", async (req, res) => {
const { message } = req.body;
try {
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: message }],
});
res.json({ reply: response.content[0].text });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Claude request failed" });
}
});
app.listen(3000, () => console.log("Server running on port 3000"));
This is the pattern most SaaS backends use: front end sends a message, backend calls Claude, backend returns text to the client. Keep the API key server-side — never call Claude directly from browser JavaScript, since your key would be exposed to anyone who opens devtools.
Step 5: Add streaming for real-time output
For chat UIs, streaming tokens as they arrive feels far more responsive than waiting for the full response. The SDK supports this natively:
app.post("/api/chat/stream", async (req, res) => {
const { message } = req.body;
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const stream = anthropic.messages.stream({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: message }],
});
stream.on("text", (text) => {
res.write(`data: ${JSON.stringify({ text })}\n\n`);
});
stream.on("end", () => {
res.write("data: [DONE]\n\n");
res.end();
});
stream.on("error", (err) => {
console.error(err);
res.end();
});
});
On the client, consume this with EventSource or a fetch reader loop, appending each chunk to your UI as it arrives.
Step 6: Handling errors and rate limits properly
Production integrations need to handle a few predictable failure modes:
- Rate limit errors (429) — back off and retry with exponential delay
- Overloaded errors (529) — Anthropic's servers are busy; retry after a short wait
- Invalid request errors (400) — usually a malformed
messagesarray or bad model name - Authentication errors (401) — expired or incorrect API key
Wrap calls in a retry helper rather than letting a transient error crash a user-facing request:
async function callWithRetry(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (err) {
if (i === retries - 1 || err.status < 500) throw err;
await new Promise((r) => setTimeout(r, 500 * 2 ** i));
}
}
}
Alternative: skip provider management with SubToAPI
The steps above work identically whether you're calling Anthropic directly or routing through SubToAPI. The difference is what happens around the API call: SubToAPI turns your existing Claude access into a standard HTTPS API with sub_live_... application keys, so instead of juggling raw provider credentials per environment, you issue scoped keys per app or teammate from one dashboard.
The request shape is the same Messages API format, so your Node.js code barely changes:
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello Claude" }],
}),
});
const data = await response.json();
console.log(data.content[0].text);
This is useful if you have multiple developers or services that need Claude access but you don't want to distribute a single shared Anthropic key. See the quickstart and messages endpoint docs for the full request/response reference, and streaming docs if you're building the real-time variant. Plans start at €9/month with a free trial — check pricing for details.
questions
Do I need the official Anthropic SDK, or can I just use fetch? Either works. The SDK adds convenience methods for streaming and retries, but the API is plain JSON over HTTPS, so fetch or axios integrate just as easily if you want fewer dependencies.
How do I keep my Claude API key secure in a Node.js app? Store it in an environment variable, load it only on the server, and never send it to the browser. All Claude calls should go through your own backend route, not directly from client-side JavaScript.
Can I use this same code with SubToAPI instead of a direct Anthropic key? Yes — swap the base URL to https://api.subtoapi.app/v1/messages and the authorization header to your sub_live_... key. The request and response format matches the Messages API, so no other code changes are needed.