← Blog

Build a Claude API Wrapper Library for Express.js

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

If you're searching for a Claude API wrapper library for Express.js, you're probably trying to solve one of two problems: you want a clean internal module that your Express routes can call without repeating fetch/auth/retry logic everywhere, or you're looking for an existing package so you don't have to write that module yourself. The short answer is that there's no single dominant "official" wrapper for Node the way there is for some other APIs, so most teams either write a thin internal wrapper (15–40 lines) or use a hosted layer that exposes an HTTP-only API they call with plain fetch.

This article covers both paths: how to structure a Claude wrapper inside an Express app yourself, and when it makes more sense to skip the wrapper entirely and call an HTTPS endpoint directly from your routes.

What a Claude wrapper for Express actually needs to do

A wrapper module sitting between your Express routes and the Claude API typically handles:

None of this is hard individually, but it adds up, and it's the kind of code that's easy to get subtly wrong (especially streaming and retry logic) and annoying to maintain across multiple routes.

A minimal wrapper module

Here's a bare-bones pattern that keeps the wrapper isolated from your route handlers:

// lib/claudeClient.js
const BASE_URL = "https://api.anthropic.com/v1/messages";

async function sendMessage({ model, messages, system, maxTokens = 1024 }) {
  const res = await fetch(BASE_URL, {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({ model, messages, system, max_tokens: maxTokens }),
  });

  if (!res.ok) {
    const body = await res.text();
    throw new Error(`Claude request failed: ${res.status} ${body}`);
  }

  return res.json();
}

module.exports = { sendMessage };
// routes/chat.js
const express = require("express");
const { sendMessage } = require("../lib/claudeClient");
const router = express.Router();

router.post("/chat", async (req, res) => {
  try {
    const result = await sendMessage({
      model: "claude-sonnet-4",
      messages: [{ role: "user", content: req.body.prompt }],
    });
    res.json(result);
  } catch (err) {
    res.status(502).json({ error: err.message });
  }
});

module.exports = router;

This is enough for a prototype. It falls apart quickly once you need streaming to the browser, multiple API keys for different environments, or usage metering per customer — at that point you're writing a real SDK, not a wrapper.

Streaming responses through Express

Streaming is where most hand-rolled wrappers get messy, because you need to pipe the upstream SSE stream through your own Express response without buffering it:

router.post("/chat/stream", async (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.flushHeaders();

  const upstream = await fetch(BASE_URL, {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      stream: true,
      messages: [{ role: "user", content: req.body.prompt }],
    }),
  });

  for await (const chunk of upstream.body) {
    res.write(chunk);
  }
  res.end();
});

This works, but you own every edge case: what happens on a dropped connection, how you detect a malformed chunk, how you reconnect. It's manageable for one route — it gets tedious once you have several.

Why some teams skip the wrapper and use a hosted layer

If what you actually want is a stable HTTPS API with keys, streaming, and usage data already solved — without maintaining the retry/auth/streaming plumbing yourself — a service like SubToAPI does that job. It turns your existing Claude access into an HTTPS API with its own sub_live_... application keys, so your Express app talks to one predictable endpoint instead of managing provider auth, retries, and streaming internals directly.

With SubToAPI, the Express side gets simpler, not more complex:

router.post("/chat", async (req, res) => {
  const upstream = 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",
      messages: [{ role: "user", content: req.body.prompt }],
    }),
  });
  res.json(await upstream.json());
});

Streaming works the same way against the streaming endpoint, and tool use follows the tools docs without you needing to build your own tool-call handling layer. If you're also running multiple services or team members, seats and keys are managed from one dashboard rather than scattered across .env files — see pricing for Solo, Team, and Scale tiers. The quickstart and messages reference cover the full request shape if you want to see exactly what's supported before switching.

Choosing between a custom wrapper and a hosted API

Build your own thin wrapper if: you have one or two routes, no streaming requirements, and no multi-key/multi-team concerns. Reach for a hosted API layer if: you need streaming in production, you're issuing different keys per environment or customer, or you want token usage visible without instrumenting it yourself.

Either way, keep the wrapper (or the client call) in a single module. Express routes should call one function or one endpoint — not construct requests inline — so that when you do need to swap providers, add retries, or move to a hosted layer, it's a one-file change.

FAQ

Is there an official Anthropic SDK for Node/Express? Yes, Anthropic publishes an official @anthropic-ai/sdk package for Node, which you can use directly inside Express routes instead of hand-rolling fetch calls — it handles request shaping and basic retries for you.

Do I need a separate wrapper if I'm just calling the API from one route? No. For a single route, inline the request or use the official SDK directly. A dedicated wrapper module pays off once you have multiple routes or need consistent error handling and logging across them.

How do I add streaming to an existing Express app without rewriting routes? Add a new route (e.g. /chat/stream) that sets SSE headers and pipes chunks as shown above, or point it at a hosted streaming endpoint like SubToAPI's streaming API — your existing non-streaming routes don't need to change.

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 →