EXPLAINER

Calling AI Services from JavaScript and TypeScript

From Node (or another server), call the model vendor with fetch, an API key from the environment, timeouts and careful JSON parsing. Expose only your own authenticated route to browsers.

Short answer: Call AI services from the server with fetch, env-based keys and error handling; let browsers call your backend only.

What is it?

JavaScript and TypeScript call model APIs the same way they call any REST service: HTTPS + JSON. The recommended place is a server runtime such as Node.js, not a public web page.

Why it matters

Correct HTTP handling determines reliability: timeouts, non-2xx bodies, and schema drift. Getting this right once lets React UIs and batch jobs share a single module.

How to do it

  1. Store the vendor key in the environment.
  2. Use fetch (Node 18+) or an HTTP library with an explicit timeout/AbortSignal.
  3. Send the provider's required headers and JSON body.
  4. On failure, capture status + body for logs (redact secrets).
  5. Parse success JSON and extract the assistant text.
  6. Optional: expose POST /api/chat that authenticates the user then runs this function.

Example

import { setTimeout as delay } from "node:timers/promises";

export async function chat(messages, { timeoutMs = 60000 } = {}) {
  const key = process.env.MODEL_API_KEY;
  if (!key) throw new Error("MODEL_API_KEY missing");
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
  try {
    const res = await fetch("https://api.example.com/v1/chat/completions", {
      method: "POST",
      signal: ctrl.signal,
      headers: {
        Authorization: `Bearer ${key}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ model: "your-model-id", messages }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(`upstream ${res.status}: ${JSON.stringify(data)}`);
    return data.choices[0].message.content;
  } finally {
    clearTimeout(t);
  }
}

For streaming responses, see Streaming AI Responses for Chat UIs and Streaming AI Responses with Node.js.

When to use it

Use this pattern for production web apps, Slack bots and server workers. Use Python instead if the rest of the pipeline is already Python (Calling AI and Model APIs with Python).

Practical notes

Production tips: cap concurrent upstream calls per user, map vendor rate-limit responses to HTTP 429 for your clients, and include a stable 'request_id' in logs. Prefer idempotency keys if you create side-effecting tool calls downstream. Keep provider-specific quirks (field names, error codes) inside one adapter file so the rest of the app speaks your own schema. If you need embeddings and chat, use two clearly named functions rather than a generic 'callAI' that mixes endpoints. Test the adapter with recorded fixtures so CI does not need live keys. For browser clients, only expose your adapter through authenticated routes described in Node.js for AI Applications. Work through one real request end to end, write down the failure modes you observed, and add a regression test so the next change does not silently drop validation, auth, or logging. Prefer boring, reviewable code over a clever abstraction until the second or third product needs the same pattern. Work through one real request end to end, write down the failure modes you observed, and add a regression test so the next change does not silently drop validation, auth, or logging. Prefer boring, reviewable code over a clever abstraction until the second or third product needs the same pattern.

Common mistakes

  • Browser-side vendor calls with exposed keys.
  • No AbortController — hung fetches.
  • Assuming choices always exists.
  • Retrying non-idempotent requests blindly without caps.