GenAI with JavaScript
Calling LLM APIs in Node.js

Calling LLM APIs in JavaScript & Node.js

Now that you understand the modern AI backend architecture, let's write code. We will use the official OpenAI Node.js SDK, but the same concepts apply whether you are connecting to OpenAI, Google Gemini, Anthropic Claude, or local models via Ollama.


1. Project Setup

Start by initializing a Node.js project and installing the required packages:

mkdir ai-backend-demo
cd ai-backend-demo
npm init -y
npm install openai dotenv

Next, create an .env file in the root directory to store your API key safely:

OPENAI_API_KEY=sk-your-actual-api-key-here

[!IMPORTANT] Never commit your .env file or hardcode your API keys into source control. Always add .env to your .gitignore.


2. Your First API Call in JavaScript

Create an index.js file and write your first completion request using modern ES modules or require:

import OpenAI from "openai";
import dotenv from "dotenv";
 
dotenv.config();
 
// The client automatically reads the OPENAI_API_KEY environment variable
const openai = new OpenAI();
 
async function main() {
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      { role: "user", content: "Explain Node.js event loop in two simple sentences." }
    ],
    temperature: 0.7,
    max_tokens: 150,
  });
 
  console.log("AI Response:\n");
  console.log(response.choices[0].message.content);
}
 
main().catch(console.error);

Run the script in your terminal:

node index.js

3. Understanding the Request Parameters

When you make a chat completion request, you pass an options object with several key configurations:

ParameterWhat It DoesRecommended Value
modelSpecifies which AI model to invoke."gpt-4o-mini" (fast & cheap), "gpt-4o" (complex tasks)
messagesThe conversation history and instructions sent to the model.Array of objects { role, content }
temperatureControls randomness / creativity (range 0.0 to 2.0).0.0 for deterministic data extraction, 0.7 for general chat
max_tokensHard limit on the maximum tokens the model can generate in its answer.300 to 1000 depending on your response needs

4. The Roles in the messages Array

The messages array is the core of how you communicate with LLMs. Each message has a specific role:

  1. system: Sets the behavior, persona, constraints, and instructions for the entire conversation.
  2. user: The prompt or question provided by the end user or your backend service.
  3. assistant: Previous responses generated by the AI model. Used to maintain multi-turn chat history.
const messages = [
  {
    role: "system",
    content: "You are a senior Node.js backend architect. Be concise and write clean code."
  },
  {
    role: "user",
    content: "How do I stream a large file in Express?"
  }
];

5. Understanding Response Structure & Tokens

When OpenAI responds, it gives you the generated text along with valuable metadata:

const response = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello!" }]
});
 
// The generated reply text
console.log(response.choices[0].message.content);
 
// Why the model stopped generating ("stop", "length", etc.)
console.log(response.choices[0].finish_reason);
 
// Token usage (crucial for tracking costs and billing)
console.log(`Prompt Tokens: ${response.usage.prompt_tokens}`);
console.log(`Completion Tokens: ${response.usage.completion_tokens}`);
console.log(`Total Tokens: ${response.usage.total_tokens}`);

What are Tokens?

Tokens are pieces of words. As a rule of thumb in English:

  • 1 token ≈ 4 characters
  • 100 tokens ≈ 75 words
  • You pay for both input tokens (your prompt) and output tokens (the AI's response).

6. Practical Real-World Example: Support Ticket Classifier

Let's build a real feature: an automated support ticket analyzer that categorizes tickets and returns guaranteed JSON.

import OpenAI from "openai";
import dotenv from "dotenv";
 
dotenv.config();
const openai = new OpenAI();
 
async function classifySupportTicket(ticketText) {
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    temperature: 0, // Deterministic for consistent classification
    response_format: { type: "json_object" }, // Guarantees valid JSON output
    messages: [
      {
        role: "system",
        content: `You are a customer support ticket triage assistant.
Analyze the incoming ticket and output a valid JSON object with:
- "category": one of ["billing", "technical", "account", "feature_request"]
- "priority": one of ["low", "medium", "high", "urgent"]
- "sentiment": one of ["positive", "neutral", "frustrated"]
- "summary": a one-sentence summary of the user issue.`
      },
      {
        role: "user",
        content: ticketText
      }
    ]
  });
 
  // Safely parse the guaranteed JSON
  const parsedData = JSON.parse(response.choices[0].message.content);
  return parsedData;
}
 
// Test our classifier
async function run() {
  const sampleTicket = "I was charged twice on my credit card for this month's subscription!";
  const result = await classifySupportTicket(sampleTicket);
  
  console.log("Classification Result:\n", result);
}
 
run();

Example Output:

{
  "category": "billing",
  "priority": "high",
  "sentiment": "frustrated",
  "summary": "Customer experienced duplicate charges on their subscription."
}

7. Production Error Handling & Retries in JavaScript

In production, external API calls will experience temporary network glitches or rate limits. Here is a reusable helper with exponential backoff:

import OpenAI from "openai";
 
const openai = new OpenAI();
 
async function callLlmWithRetry(messages, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: messages,
        timeout: 20000 // 20-second timeout
      });
 
      return response.choices[0].message.content;
    } catch (error) {
      if (error instanceof OpenAI.RateLimitError) {
        const waitTimeMs = Math.pow(2, attempt) * 1000;
        console.warn(`Rate limited. Retrying in ${waitTimeMs / 1000}s... (Attempt ${attempt}/${maxRetries})`);
        await new Promise((resolve) => setTimeout(resolve, waitTimeMs));
      } else if (error instanceof OpenAI.APIConnectionTimeoutError) {
        console.warn(`Request timed out. Retrying attempt ${attempt}...`);
      } else {
        // Unrecoverable error (e.g. invalid API key, malformed request)
        console.error("Unrecoverable OpenAI Error:", error.message);
        throw error;
      }
    }
  }
 
  throw new Error(`Failed after ${maxRetries} retry attempts.`);
}

8. Cost Management & Model Selection (2025)

ModelPrimary Best Use CaseApproximate Cost
gpt-4o-mini90% of backend tasks: classification, summarization, extraction🟢 Very Low ($)
gpt-4oComplex multi-step reasoning, architectural analysis, complex coding🟡 Moderate ($$)
claude-3-5-sonnetExtremely nuanced text writing, long document synthesis🟡 Moderate ($$)
gemini-1.5-flashHigh-throughput simple tasks, multimodal analysis🟢 Very Low ($)

4 Rules for Cost Optimization:

  1. Default to mini models: Always test with gpt-4o-mini first.
  2. Always set max_tokens: Prevents the model from generating accidental runaway essays.
  3. Cache responses: If a query has temperature: 0 and identical input, cache the result in Redis.
  4. Trim prompt bloat: Every unnecessary sentence in your system prompt costs tokens on every single user request.

👉 Next Step: Learn how to write bulletproof developer prompts in Prompt Engineering for JavaScript Developers.