// LOADING
// LOADING
// LOADING_ARTICLE
Before you can build semantic search, a retrieval-augmented generation system, or a recommendation engine, you need to understand embeddings. They are the numerical foundation that makes all of these features possible.
This post explains what embeddings are, how similarity search works, and how to put them to use in a Node.js application.
An embedding is a fixed-length array of floating-point numbers that represents the meaning of a piece of text. Two pieces of text with similar meanings produce vectors that are close to each other in high-dimensional space; unrelated texts produce vectors that are far apart.
This is fundamentally different from keyword matching. The phrase "automobile accident" and "car crash" use no common words, but their embeddings will be close because they describe the same concept.
Embedding models are trained to capture this semantic proximity. You pass them a string and receive a vector, typically several hundred to a few thousand dimensions long. You never inspect the individual numbers — you only compute distances between vectors.
Anthropic's API does not currently expose a dedicated embeddings endpoint, so in practice you pair Claude with a separate embeddings model. A common choice for Node.js projects is the text-embedding-3-small model from OpenAI or the open-weight nomic-embed-text model served locally. What matters architecturally is that:
Here is a minimal embeddings utility that works with any REST-compatible endpoint:
tsasync function embed(text: string): Promise<number[]> { const res = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "text-embedding-3-small", input: text, }), }); const data = await res.json(); return data.data[0].embedding as number[]; }
Store these vectors alongside your documents in a vector database or in a column that supports vector search.
Given a query embedding and a collection of stored embeddings, similarity search returns the stored vectors that are closest to the query vector. Two distance metrics dominate:
Measures the angle between two vectors. Values range from -1 (opposite directions) to 1 (identical direction). For normalised vectors, cosine similarity is equivalent to dot-product similarity and is the most common choice for semantic search:
tsfunction cosineSimilarity(a: number[], b: number[]): number { const dot = a.reduce((sum, ai, i) => sum + ai * b[i], 0); const magA = Math.sqrt(a.reduce((s, v) => s + v * v, 0)); const magB = Math.sqrt(b.reduce((s, v) => s + v * v, 0)); return dot / (magA * magB); }
The straight-line distance between two points. Useful when magnitude matters, but for most NLP tasks cosine similarity performs better because text embeddings are typically high-dimensional and unit-normalised.
Brute-force cosine similarity over millions of vectors is too slow for production. Vector databases solve this with Approximate Nearest Neighbour (ANN) algorithms like HNSW and IVF, which trade a small amount of recall for orders-of-magnitude faster query times.
Popular vector stores:
For a deeper comparison of these options, see Vector Databases and How Semantic Search Works.
The quality of search results depends heavily on how you split documents:
A practical starting point is 300-500 token chunks with 50-100 token overlap between adjacent chunks. The overlap ensures that sentences near chunk boundaries appear in at least one full chunk.
tsfunction chunkText(text: string, chunkSize = 400, overlap = 80): string[] { const words = text.split(/\s+/); const chunks: string[] = []; let i = 0; while (i < words.length) { chunks.push(words.slice(i, i + chunkSize).join(" ")); i += chunkSize - overlap; } return chunks; }
After chunking, embed each chunk individually and store it with metadata pointing back to the source document.
Embeddings are the retrieval layer in RAG. The pipeline is:
This keeps the LLM grounded and is one of the most effective ways to reduce hallucinations. The full pattern is covered in Retrieval-Augmented Generation (RAG), Explained.
Embeddings are not only for search. Other common applications:
For a full end-to-end implementation of blog search using embeddings, see Adding AI-Powered Search to Your Blog with Embeddings.
You can see embedding-based features in action on the /projects page.
Embeddings convert text into a form where mathematical distance equals semantic similarity. Everything that makes modern AI search feel intelligent — the ability to match intent rather than keywords, to find related content, to power RAG — traces back to this idea. Understanding embeddings is not optional if you are building AI features; it is the bedrock concept everything else builds on.