If your app already runs on Postgres and you need a search box over a few hundred thousand rows, start with Postgres full-text search plus pg_trgm. Add Meilisearch or Typesense when you need typo tolerance as-you-type, faceted filtering with instant counts, or relevance tuning that non-engineers can adjust. Until one of those is a real requirement, a second datastore is mostly a second thing to keep in sync.
What does Postgres full-text search actually give you?
More than people assume. Stemming, stop words, weighted fields, phrase search, ranking, and a websearch_to_tsquery parser that understands quoted phrases and -exclusions the way users type them. As of late 2026 this is all core Postgres, no extension needed.
A setup that works well for a typical articles table:
ALTER TABLE articles ADD COLUMN search tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(body, '')), 'B') ) STORED; CREATE INDEX articles_search_idx ON articles USING GIN (search); SELECT id, title, ts_rank(search, q) AS rank FROM articles, websearch_to_tsquery('english', 'postgres "connection pool" -mysql') AS q WHERE search @@ q ORDER BY rank DESC LIMIT 20; The generated column keeps the index in sync with the row inside the same transaction. That is the real advantage over an external service: there is no indexing pipeline that can fall behind or silently drop a document.
Takeaway: Postgres full-text search gives you transactionally consistent, weighted, stemmed search with zero new infrastructure.
Where does Postgres search fall short?
Three places, and they are the ones users notice.
Typos. Searching postgre or kuberentes returns nothing, because full-text search matches lexemes, not approximate strings. The standard patch is pg_trgm:
CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX articles_title_trgm ON articles USING GIN (title gin_trgm_ops); SELECT id, title, similarity(title, 'kuberentes') AS sim FROM articles WHERE title % 'kuberentes' ORDER BY sim DESC LIMIT 10; This works for short fields like titles and names. It is a poor fit for fuzzy matching inside long bodies, and tuning pg_trgm.similarity_threshold is something you do by trial and error.
Ranking quality. ts_rank does not account for how rare a term is across the corpus in the way BM25-style engines do. For small, homogeneous data you may never care. For mixed content, results can feel arbitrary, and the fix is hand-written boosts in SQL.
Faceting. Counting results per category for every query means a GROUP BY over the whole match set. That is fine at tens of thousands of matches and gets slow beyond that.
Takeaway: Postgres fails at the user-facing polish (typos, ranking, facet counts), not at the correctness of matching.
How do Meilisearch and Typesense differ?
Both are single-binary, open-source search engines aimed at the "instant search box" use case, both ship typo tolerance on by default, and both have hosted cloud offerings if you do not want to run them. The differences that matter in practice:
| Postgres FTS + pg_trgm | Meilisearch | Typesense | |
|---|---|---|---|
| New infrastructure | None | One service | One service (cluster for HA) |
| Typo tolerance | Only via pg_trgm, manual | Built in | Built in |
| Index lives in | Your database | Disk (memory-mapped) | RAM |
| Consistency with your DB | Transactional | Eventually, you sync it | Eventually, you sync it |
| Facets / filters | Hand-written SQL | Built in | Built in |
| Ranking control | SQL expressions | Ordered ranking rules | Per-field weights, sort expressions |
| Main drawback | Polish, tuning effort | Another sync pipeline | RAM sizing, another sync pipeline |
Typesense holding its index in RAM is why it is fast, and it is also the cost driver: your dataset size becomes your memory bill. Meilisearch is lighter on RAM for the same corpus in my experience, but check the current license terms for any specific feature you depend on, since parts of the ecosystem have moved between licenses over time.
If you want the managed, zero-ops version of instant search with typo tolerance, Meilisearch Cloud is the option that removes the hosting question while keeping the engine simple. If you want predictable low-latency lookups and your dataset comfortably fits in memory, Typesense is the one built around that assumption.
Takeaway: Meilisearch and Typesense buy you polish, and the price is a sync pipeline plus one more thing to operate.
What is the hidden cost of adding a search service?
Keeping it in sync. Every create, update, and delete in Postgres must reach the engine, and the failure mode is quiet: a deleted record that still shows up in results, or a new one that never appears.
The minimum viable pattern is an outbox table written in the same transaction as the change, drained by a worker:
CREATE TABLE search_outbox ( id bigserial PRIMARY KEY, article_id bigint NOT NULL, op text NOT NULL CHECK (op IN ('upsert', 'delete')), created_at timestamptz NOT NULL DEFAULT now() ); The worker reads in batches, pushes to the engine, then deletes the processed rows. You also need a periodic full reindex, because you will eventually find drift. That is roughly a day of work plus ongoing monitoring, which is the honest comparison against the afternoon Postgres FTS takes.
Takeaway: The search engine is cheap; the sync pipeline and its drift detection are what you actually pay for.
Which one should you pick?
| Your situation | Pick |
|---|---|
| Under roughly a few hundred thousand rows, users search by exact-ish terms | Postgres FTS |
| Short fields (names, titles) where typos matter | Postgres FTS + pg_trgm |
| Search-as-you-type with typo tolerance is a product requirement | Meilisearch or Typesense |
| Many facets with live counts | Meilisearch or Typesense |
| Dataset is small enough to fit in RAM and latency is critical | Typesense |
| You have no one to operate another service | Postgres, or a managed cloud plan |
These size boundaries are rules of thumb, not benchmarks. Measure your own query latency with EXPLAIN (ANALYZE, BUFFERS) before deciding the database is the bottleneck.
FAQ
Is Postgres full-text search good enough for production?
Yes, for keyword search over moderate datasets it is production-grade, with GIN indexes and weighted ranking. It becomes a poor fit when you need typo tolerance in long text or live facet counts.
How do I add typo tolerance to Postgres search?
Enable the pg_trgm extension, add a GIN trigram index on the field, and query with the % similarity operator. It works best on short fields like titles.
Meilisearch or Typesense: which is easier to start with?
Both run as a single binary and take minutes to try locally. Meilisearch is usually lighter on memory, while Typesense keeps its index in RAM, so size your server to your data.
Bottom line
If you already have Postgres and a search box, use tsvector with a GIN index and add pg_trgm for names and titles; you can ship that in an afternoon and it stays consistent with your data. Move to Meilisearch or Typesense when typo-tolerant instant search or faceting becomes something users complain about, and budget for the outbox and reindex job from day one. Pick the engine after you have measured what your data size and latency actually need.