n4nAI

Generating embeddings with Node.js and the OpenAI API

Learn how to generate OpenAI embeddings with Node.js: project setup, batching, model choices, error retries, and a cosine-similarity check to verify output.

n4n Team3 min read615 words

Audio narration

Coming soon — every post will get a voice note here.

Semantic search and retrieval-augmented generation start with turning text into vectors. This guide builds a complete, production-minded script for openai embeddings node.js using the official SDK, covering model selection, batching, and a verification routine you can trust. You will go from an empty directory to a tested embedding pipeline.

Step 1: Initialize the Node.js project and install dependencies

Use Node.js 18 or newer so the OpenAI SDK’s built-in fetch works without polyfills. Create a fresh directory and install the packages.

mkdir embed-demo && cd embed-demo
npm init -y
npm install openai dotenv

Create a .env file to hold the key:

echo "OPENAI_API_KEY=sk-your-key" > .env

In your entry script (index.mjs), load the environment and instantiate the client. Using ESM ("type": "module" in package.json) keeps the syntax clean.

import 'dotenv/config';
import OpenAI from 'openai';

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

The client defaults to the official API. If you later route through a gateway, only the baseURL changes.

Step 2: Make your first embedding request

The embeddings endpoint accepts a model name and an input string. text-embedding-3-small is the default cost-effective option for most workloads.

async function embedOne(text) {
  const res = await client.embeddings.create({
    model: 'text-embedding-3-small',
    input: text,
  });
  return res.data[0].embedding;
}

const vec = await embedOne('Hello world');
console.log(`Vector length: ${vec.length}`);

The response shape is stable:

{
  "object": "list",
  "data": [{ "object": "embedding", "index": 0, "embedding": [0.001, -0.023] }],
  "model": "text-embedding-3-small",
  "usage": { "prompt_tokens": 2, "total_tokens": 2 }
}

A successful call prints a vector of 1536 floats for text-embedding-3-small. The usage field reports token count, which is what you are billed on. That confirms the openai embeddings node.js call works.

Step 3: Choose the right model and dimensions

OpenAI ships text-embedding-3-small (1536 dims) and text-embedding-3-large (3072 dims). The large model scores higher on public retrieval benchmarks but costs more per token. Both support a dimensions parameter to truncate the vector, reducing storage and compute at a small accuracy cost.

async function embedWithDims(text, dims) {
  const res = await client.embeddings.create({
    model: 'text-embedding-3-large',
    input: text,
    dimensions: dims,
  });
  return res.data[0].embedding;
}

const shortVec = await embedWithDims('Quantize this', 1024);
console.log(shortVec.length); // 1024

Pick dimensions based on your vector store and latency budget. Smaller vectors speed up cosine search but may blur semantic nuance. The raw vectors are not normalized by the API, so you must normalize before computing cosine similarity.

Step 4: Batch multiple inputs efficiently

The API accepts an array of strings in a single request. Batching cuts HTTP overhead and is the correct way to process corpora.

async function embedBatch(texts) {
  const res = await client.embeddings.create({
    model: 'text-embedding-3-small',
    input: texts,
  });
  return res.data.map((d) => d.embedding);
}

For large corpora, slice into chunks and process sequentially to avoid 429s:

async function embedCorpus(texts, chunkSize = 100) {
  const out = [];
  for (let i = 0; i < texts.length; i += chunkSize) {
    const slice = texts.slice(i, i + chunkSize);
    out.push(...await embedWithRetry(slice));
  }
  return out;
}

Each input must stay under the model’s token limit (8191 tokens for these models). If you need concurrency, cap it at 3–5 simultaneous requests; the retry logic below pairs well with that.

Step 5: Handle errors and retries

Rate limits and transient 5xx errors happen. The SDK throws APIError with a status field. Wrap the call with exponential backoff.

import { APIError } from 'openai';

async function embedWithRetry(texts, attempts = 4) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await embedBatch(texts);
    } catch (err) {
      if (err instanceof APIError && (err.status === 429 || err.status >= 500)) {
        const delay = 2 ** i * 200 + Math.random() * 200;
        await new Promise((r) => setTimeout(r, delay));
        continue;
      }
      throw err;
    }
  }
  throw new Error('Embedding failed after retries');
}

Embeddings are deterministic for a given model and input, so retrying is safe. The SDK also exposes maxRetries and timeout constructor options, but a custom backoff gives finer control over jitter and logging. Do not cache blindly without keying on model + input + dimensions.

Step 6: Normalize and compute similarity

To verify the vectors capture meaning, compute cosine similarity. Normalize first so the dot product equals cosine.

function normalize(v) {
  const norm = Math.sqrt(v.reduce((s, x) => s + x * x, 0));
  return v.map((x) => x / norm);
}

function cosine(a, b) {
  const na = normalize(a);
  const nb = normalize(b);
  return na.reduce((s, x, i) => s + x * nb[i], 0);
}

const v1 = await embedOne('puppy');
const v2 = await embedOne('dog');
const v3 = await embedOne('spaceship');

console.log('puppy vs dog:', cosine(v1, v2).toFixed(3));
console.log('puppy vs spaceship:', cosine(v1, v3).toFixed(3));

Cosine ranges from -1 to 1. Expected output shows a high score (typically >0.8) for the first pair and a low score (often <0.2) for the second. That is your proof the openai embeddings node.js pipeline produces semantically useful vectors.

Step 7: Route through a gateway for resilience

In production, provider outages waste cycles. If you point the same client at an OpenAI-compatible endpoint such as n4n.ai, you get automatic fallback when a provider is rate-limited or degraded, plus per-token usage metering. The code change is one line:

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: 'https://api.n4n.ai/v1',
});

The gateway honors your model string and forwards cache-control hints, so the rest of your embedding code stays identical. This is useful when you want to address 240+ models behind one base URL without rewriting clients.

Step 8: Verify the pipeline end-to-end

Write a small self-test that asserts shape and similarity. Run it with node index.mjs.

async function selfTest() {
  const a = await embedOne('climate change');
  const b = await embedOne('global warming');
  const c = await embedOne('basketball hoop');
  if (a.length !== 1536) throw new Error('wrong dims');
  const sim = cosine(a, b);
  if (sim < 0.7) throw new Error('semantic match too low');
  console.log('All checks passed; similarity=', sim.toFixed(3));
}

selfTest().catch((e) => { console.error(e); process.exit(1); });

Add a script to package.json:

{ "scripts": { "start": "node index.mjs" } }

Run npm start. If it logs the pass line, your openai embeddings node.js integration is complete and validated. From here, persist vectors to pgvector or a dedicated index and query them in your application.

Tagsopenaiembeddingsnodejsjavascript

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All embeddings api integration across languages posts →