← Blog

Anthropic Claude Integration in Node.js + Express

2026-11-08 · 5 min read · SubToAPI Team

Integrating Claude Into a Node.js Express App

If you're building a Node.js backend with Express and want to add Claude for chat, summarization, or agent-style features, the integration itself is straightforward: you send a POST request with your message history to an HTTPS endpoint, parse the JSON response, and return it to your frontend. The parts that actually take time are authentication, streaming responses back to the browser, handling tool use, and keeping your API key out of client code.

This guide walks through a working Express setup for Claude, covering the official Anthropic SDK pattern, streaming with Server-Sent Events, and what changes if you're using a proxy like SubToAPI instead of managing Anthropic credentials directly.

Basic Setup: Express Route Calling Claude

Install the Anthropic Node SDK and Express:

npm install @anthropic-ai/sdk express

A minimal route that accepts a user message and returns Claude's reply:

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) => {
  try {
    const { message } = req.body;

    const response = await anthropic.messages.create({
      model: "claude-3-5-sonnet-latest",
      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 pattern works fine for prototypes and internal tools. For anything user-facing, you'll want to add a few things on top: request validation, rate limiting per user, retry logic for transient errors, and ideally a layer that tracks usage per API consumer rather than per raw API key.

Managing Multiple Conversation Turns

Claude's API is stateless — you send the full message history on every call. A simple pattern is to keep conversation state server-side (in a session store, Redis, or your database) and append new turns:

app.post("/api/chat/:sessionId", async (req, res) => {
  const { sessionId } = req.params;
  const { message } = req.body;

  const history = await getHistory(sessionId); // your own storage logic
  history.push({ role: "user", content: message });

  const response = await anthropic.messages.create({
    model: "claude-3-5-sonnet-latest",
    max_tokens: 1024,
    messages: history,
  });

  history.push({ role: "assistant", content: response.content[0].text });
  await saveHistory(sessionId, history);

  res.json({ reply: response.content[0].text });
});

Keep an eye on token growth — long conversations eventually need truncation or summarization before they hit context limits.

Streaming Responses to the Frontend

For chat UIs, streaming tokens as they're generated makes a big difference in perceived latency. Express can relay a stream using Server-Sent Events:

app.get("/api/chat/stream", async (req, res) => {
  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-3-5-sonnet-latest",
    max_tokens: 1024,
    messages: [{ role: "user", content: req.query.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 frontend, consume this with EventSource or a fetch call that reads the response body as a stream. The key thing to get right in Express is setting the headers before writing any data, and making sure nothing buffers the response (some reverse proxies need X-Accel-Buffering: no for this to work correctly in production).

Tool Use From an Express Backend

If your app needs Claude to call functions — looking up a database record, hitting an internal API, running a calculation — you define tools with a JSON schema and handle the tool_use content block in your route:

const response = await anthropic.messages.create({
  model: "claude-3-5-sonnet-latest",
  max_tokens: 1024,
  tools: [
    {
      name: "get_order_status",
      description: "Look up the status of an order by ID",
      input_schema: {
        type: "object",
        properties: { orderId: { type: "string" } },
        required: ["orderId"],
      },
    },
  ],
  messages: [{ role: "user", content: "Where is order 4821?" }],
});

const toolUse = response.content.find((b) => b.type === "tool_use");
if (toolUse) {
  const result = await lookupOrder(toolUse.input.orderId);
  // send result back as a tool_result message in the next request
}

Express fits naturally here since you're just chaining normal async function calls between Claude's tool request and your own business logic.

Where SubToAPI Fits In

If you're building this integration for a product with multiple users or team members — not just a personal script — managing raw Anthropic API keys gets messy fast: everyone shares one key, usage isn't broken out per app or per team member, and rotating a compromised key means touching every deployment.

SubToAPI sits between your Express app and Claude as a drop-in HTTPS layer. Instead of an Anthropic key, your app uses a sub_live_... key scoped to your application, with the same request shape your Express routes already expect:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-3-5-sonnet-latest",
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "Summarize this ticket." }]
  }'

You get streaming, tool use, and usage metadata per key without rewriting your Express routes — just change the base URL and the key. See the quickstart and messages docs for the full request format, and streaming and tools for those specific flows. Plans start at €9/month, with a free trial at signup.

FAQ

Do I need the official Anthropic SDK to use Claude in Express, or can I just use fetch/axios? The SDK isn't required — Claude's API is plain HTTPS/JSON, so fetch, axios, or node-fetch work fine. The SDK mainly saves you from hand-rolling streaming parsing and retry logic.

How do I keep my Claude API key safe in an Express app? Never expose it to the frontend. Store it in an environment variable, load it with dotenv or your platform's secrets manager, and only call Claude from server-side routes. If multiple apps or team members need access, scoped keys (like SubToAPI's sub_live_ keys) are safer than sharing one Anthropic key.

Can Express handle streaming Claude responses without extra libraries? Yes. Node's native streaming plus Express's res.write() is enough to relay Server-Sent Events to the browser — no additional streaming library is required, though you do need to disable response buffering in any reverse proxy sitting in front of your server.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →