Home / Articles

Semantic Search in PostgreSQL With pgvector and Node.js, No LLM Needed

Semantic search in PostgreSQL with pgvector and Node.js, no LLM: MiniLM embeddings, chunks, an IVFFlat index, distance operators and how to read the scores.

Publisher: REACTAPP.TOP8 min read1884 words

This article is published in English.

Sources: Talk to Your Database Like a Human — No LLM Required: Semantic Search with Node.js + PostgreSQL

A database can answer questions asked in ordinary English without a language model anywhere in the loop. What makes it possible is vector search. With Node.js, PostgreSQL and the pgvector extension, you can build a system that matches on intent instead of keywords, and the only model involved is a small embedding model that turns text into numbers.

Relational databases have always been excellent at storing and fetching data, provided you ask exactly the right question. Exact matches, filters and structured queries are powerful but narrow. Vector databases appeared to deal with a different problem: searching unstructured content by what it means, not by which words it contains.

Four terms worth getting straight

Token. The smallest piece of text a model works with. Tokens aren't always whole words. A token may be a full word such as "hello", a fragment (so "unbelievable" could become "un", "believ" and "able"), a single character like "!" or a bit of punctuation or whitespace. A phrase like "ChatGPT is great" might be cut into "Chat", "G", "PT", " is" and " great".

Chunk. A small, self-contained slice of a larger document, usually a few sentences or one paragraph. Embedding a whole document in one go works poorly, so you split it first. Chunking speeds up processing, sharpens search accuracy and keeps results relevant to the query.

Embedding. A numerical stand-in for a piece of text, image or audio that captures what it means. A machine learning model, MiniLM for example, produces it. You can think of an embedding as the idea behind the data.

Vector. The way an embedding is actually held: an array of numbers.

[0.2, -0.5, 0.9, ...]

That is the form kept in memory and in PostgreSQL once an extension such as pgvector is installed.

Put together, the idea is this. Words, sentences or images become embeddings. Things with similar meanings end up with vectors that sit close together, and unrelated things end up farther apart. A string like "Hello world" turns into a long list of floats:

// "Hello world" turns into this embedding:
const embedding = [0.01, -0.42, 0.15, ..., 0.99]; // ← this is a vector

and that list is then saved next to the original text:

INSERT INTO documents (content, embedding)
VALUES ('Hello world', embedding);

Why a relational database isn't enough

A traditional RDBMS works with tables, rows and columns. It handles requests such as "all tickets with status OPEN" or "users created after 2024" without effort. Modern applications, though, also deal with documents, conversations, images and audio. Exact matching can't do much with those.

A vector database stores embeddings alongside the data and finds items by similarity and context. So instead of asking for documents containing the word "error", you can ask for documents about authentication problems, and get them even when "error" never appears. The comparison is a mathematical distance between vectors, not a text match.

The usual pipeline looks like this:

  1. Raw content comes in: documents, tickets or anything else.
  2. An embedding model converts it to vectors.
  3. The vectors are stored, for instance in PostgreSQL with pgvector.
  4. An approximate nearest neighbor (ANN) index keeps lookups fast.
  5. The user's query is converted to a vector too.
  6. The database returns the vectors closest to it, meaning the most similar meaning.

What this enables:

  • Semantic search, driven by the meaning of a query and not only its exact words.
  • Long-term memory for an LLM, by keeping past interactions, documents or facts that the model can recall even though they weren't in its training data.
  • Recommendations, by finding items or behavior that resemble each other in embedding space.
  • Similarity search over text, images, audio or video.

PostgreSQL as the vector store

You don't have to adopt a new database for any of this. With pgvector, PostgreSQL can store embeddings directly, run similarity queries and use indexes such as IVFFlat to stay fast. The database you already run simply grows a new ability, from holding data to being able to compare its meaning. Searching stops being about matching words and starts being about matching meaning.

Setting it up step by step

1. Install PostgreSQL and enable pgvector. Install PostgreSQL from the official download page, connect, and turn on the extension:

CREATE EXTENSION IF NOT EXISTS vector;

2. Create the documents table. It holds the raw text and its embedding. The 384 in the definition is the output size of the all-MiniLM-L6-v2 model.

-- Note: 384 is the embedding size output by all-MiniLM-L6-v2
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  embedding VECTOR(384)
);

3. Add a vector index. To keep similarity search efficient, use an approximate nearest neighbor index:

CREATE INDEX documents_embedding_idx
ON documents
USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);

This picks IVFFlat for fast approximate lookups, measures with L2 (Euclidean) distance and divides the vectors into 100 clusters.

4. Match the query operator to the index. The operator in your query has to agree with the operator class you built the index with. pgvector offers several operators, each paired with its own class. The example here queries with <#> and configures vector_l2_ops. Be aware that in pgvector's documentation L2 distance normally goes with <->, and <#> is the negative inner product, so check that your own query and index line up.

5. Run the project. A complete working example sits in a GitHub repository named vectorgarquer/vectorized-database-embeddings. Clone or fork it, then install dependencies:

npm install

Copy .env.example to a .env file in the project root and fill in your PostgreSQL connection details. Then start it:

npm start

6. Watch it search. Once running, the program connects, inserts embeddings and runs semantic searches without further input. From there you can feed it your own data and queries. The sample searches for "What does mean generative AI?", and the matches it finds in the database come back like this:

Search Results:

[1] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[2] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[3] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[4] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[5] Distance: -0.7094

assistants,  personalized  education  and  service,  and  digital
art. As such, generative AI has substantial implications for
BISE  practitioners  and  scholars  as  an  interdisciplinary
research community. In our Catchword article, we offered
a conceptualization of the principles of generative AI along
a model-, system-, and application-level view as well as a
social-technical  view  and  described  limitations  of  current
generative   AI.   Ultimately,   we   provided   an   impactful
rese

Reading the output

After a run you get a list shaped like this:

Search Results:

[1] Distance: -0.7562
...
[5] Distance: -0.7094
...

The distance says how close a stored chunk is to the query embedding. Both the chunk and the query are turned into vectors, and PostgreSQL with pgvector measures how far apart they are. The rule is simple: the smaller the distance, the more similar the content.

The values are negative, such as:

-0.7562

That's because the <#> operator returns a negated value so that an ordinary sort puts the closest rows first. In practice a more negative number means a closer, more relevant match, and a number nearer zero means a weaker one.

Looking at the ranking, results 1 to 4 share the same score of -0.7562, so they are equally relevant to the query. Result 5 scores -0.7094, which is still relevant but a little less similar.

You may also notice several results with near-identical text. That is expected. The source document was split into chunks, neighboring chunks overlap or repeat similar content, and each one has its own embedding.

What the result shows

With no LLM at query time, the system still understood meaning and not just keywords, pulled back text that fits the context and ranked it by vector similarity. The returned passages clustered around generative AI models, a model-level view and machine learning architecture, so the search clearly worked at the level of concepts instead of exact words.

The outcome is a semantic search engine that lives inside PostgreSQL. No LLM is needed when you query, there is no separate vector database, and embeddings plus pgvector do all the work.