← Blog/Field Manual Series/Article 02 · Week 2

Postgres Can Do Typo-Tolerant Search. You Don't Need Elasticsearch Yet.

Omenabyte Intelligence·Aug 23, 2026·5 min read
Just Use Postgres — The Field Manual for Replacing MongoDB, Redis, Elasticsearch & More

A search bar feels like an Elasticsearch problem the moment someone asks for "did you mean...?" or relevance ranking. So the data gets duplicated into a second cluster, a sync job gets built to keep it current, and now there are two sources of truth for the same rows.

Postgres already has both stemmed search and typo tolerance built in — one native, one a very old, very stable extension.

Stemmed, ranked search with tsvector

tsvector strips stop words and reduces text to root forms — "running" becomes "run" — so a search for one form matches the others, ranked by relevance.

search.sql — generated column + GIN index
ALTER TABLE articles ADD COLUMN search_vector tsvector
  GENERATED ALWAYS AS (
    to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''))
  ) STORED;

CREATE INDEX idx_articles_search ON articles USING GIN (search_vector);

SELECT title FROM articles
WHERE search_vector @@ to_tsquery('english', 'postgres & scaling')
ORDER BY ts_rank(search_vector, to_tsquery('english', 'postgres & scaling')) DESC;

That coalesce() isn't decoration — I found this the hard way. In Postgres, text || NULL evaluates to NULL. Skip the coalesce, and the very first article with an empty body silently drops out of search entirely, with no error to tell you why.

Typo tolerance with pg_trgm

For the "did you mean" experience, pg_trgm breaks text into overlapping three-letter chunks and matches on similarity instead of exact spelling.

trgm.sql — similarity matching
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_articles_title_trgm ON articles USING GIN (title gin_trgm_ops);

SELECT title FROM articles
WHERE 'Postgress' <% title  -- misspelled, still matches
ORDER BY word_similarity('Postgress', title) DESC;

Notice this uses <% (word_similarity), not the more commonly-shown % (similarity). That distinction actually matters: % compares two entire strings against each other. Test it yourself — a completely ordinary title like "Scaling Postgres in Production" scores below the default match threshold against "Postgress," because the operator is comparing the misspelled word to the whole sentence, not to the word within it. word_similarity checks the query against individual words inside the longer string instead, which is what "typo-tolerant search" actually means in practice.

Why this beats standing up a search cluster

  • The index lives transactionally with the row. No reindex pipeline to keep in sync.
  • Ranking is built in via ts_rank, not bolted on.
  • One fewer cluster to provision, patch, and pay for.

Where Elasticsearch still wins

Distributed log analytics at serious scale, or if you're already deep in its observability ecosystem (Kibana dashboards, log aggregation) rather than a single application search bar. For "let users search my app's content," Postgres covers the overwhelming majority of real cases.

Field Manual Series · Every recipe tested

This is one of eight infrastructure swaps.

Just Use Postgres is a 24-page field manual on replacing MongoDB, Redis, Elasticsearch, Pinecone, and more with the database you're probably already running. Every recipe in it — including this one — was run against a live Postgres instance before it went in the book.

$14 once vs. $50/month managed search — the math writes itself.

Up next in the series

Replace Pinecone With pgvector + HNSW — embeddings included.

See the full series →