The Right Architecture

Browser to your server to Claude. The API key stays on the server, always.

Every AI feature you build follows the same shape: the browser talks to your server, and your server talks to Claude. The browser never talks to Claude directly.

Why the middle layer

  • Security — the API key lives on the server, never in the browser.
  • Control — you add auth, rate limits, and validation.
  • Flexibility — you can log, cache, and shape prompts before they reach Claude.
Browser to your server to Claude API, with the API key kept server-sideBrowserno API keyYour serverAPI key lives hereClaude APIapi.anthropic.comfetch()x-api-key
The browser calls your endpoint; only your server holds the API key and calls Claude.

Example

Example · javascript
// server.js — your backend endpoint the browser calls
import express from "express";
import Anthropic from "@anthropic-ai/sdk";

const app = express();
app.use(express.json());
const client = new Anthropic(); // key stays here, on the server

app.post("/api/ask", async (req, res) => {
  const result = await client.messages.create({
    model: "claude-opus-4-8", max_tokens: 1024,
    messages: [{ role: "user", content: req.body.question }],
  });
  const text = result.content.find((b) => b.type === "text")?.text ?? "";
  res.json({ answer: text });
});

app.listen(3000);

When to use it

  • Build a Next.js app with an /api/ask route that proxies user messages to Claude, keeping the key server-side.
  • Create an Express middleware layer that rate-limits requests before forwarding them to the Anthropic API.
  • Deploy your Claude backend as a separate microservice so multiple frontend apps share one secure gateway.

More examples

Next.js API route as Claude proxy

The API route sits between the browser and Anthropic — the key never leaves the server.

Example · javascript
// src/app/api/ask/route.ts
import { NextRequest, NextResponse } from "next/server";
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic(); // key from env — never sent to browser

export async function POST(req: NextRequest) {
  const { question } = await req.json();
  const msg = await client.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 512,
    messages: [{ role: "user", content: question }],
  });
  return NextResponse.json({ answer: msg.content[0].text });
}

Browser calls your server, not Claude

The browser only knows about your /api/ask endpoint — the Anthropic URL and key are never visible in the client.

Example · javascript
// Browser-side fetch — calls YOUR server, not Anthropic
async function ask(question) {
  const res = await fetch("/api/ask", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ question }),
  });
  const { answer } = await res.json();
  return answer;
}

Add auth before proxying to Claude

Checks the session before calling Claude so unauthenticated users cannot consume API credits.

Example · javascript
export async function POST(req: NextRequest) {
  const session = await getServerSession();
  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const { question } = await req.json();
  const msg = await client.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 256,
    messages: [{ role: "user", content: question }],
  });
  return NextResponse.json({ answer: msg.content[0].text });
}

Discussion

  • Be the first to comment on this lesson.