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
- Store the vendor key in the environment.
- Use
fetch(Node 18+) or an HTTP library with an explicit timeout/AbortSignal. - Send the provider's required headers and JSON body.
- On failure, capture status + body for logs (redact secrets).
- Parse success JSON and extract the assistant text.
- Optional: expose
POST /api/chatthat 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
choicesalways exists. - Retrying non-idempotent requests blindly without caps.