Search starts as WHERE title LIKE '%term%'. It works until someone misspells a word, searches two words in the wrong order, or expects results as they type — and then the options look like a week with Elasticsearch or a bill from Algolia.

Meilisearch sits between them: a single Rust binary, typo-tolerant and fast by default, with an API you can learn in an afternoon. Open source, self-hostable, with a paid cloud if you’d rather not run it.

Run and index

  docker run -p 7700:7700 -e MEILI_MASTER_KEY=aSampleMasterKey \
  getmeili/meilisearch:latest
  
  npm install meilisearch
  
  import { MeiliSearch } from 'meilisearch';
const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: process.env.MEILI_KEY });

const index = client.index('posts');

await index.addDocuments([
  { id: 1, title: 'Deploying with Railway', tags: ['deploy'], published_at: 1735689600 },
  { id: 2, title: 'Postgres branching with Neon', tags: ['database'], published_at: 1738368000 },
]);

const results = await index.search('postgrs branchng');   // typos are fine
  

That last line is the demo that sells it: misspellings still match, and the response comes back in a handful of milliseconds.

Configure the three things that matter

  await index.updateSettings({
  searchableAttributes: ['title', 'summary', 'body'],   // order = importance
  filterableAttributes: ['tags', 'author_id', 'published'],
  sortableAttributes: ['published_at'],
});
  
  • searchableAttributes in priority order — a match in the title should outrank one buried in the body.
  • filterableAttributes for anything you’ll narrow by. Filters are exact; search is fuzzy.
  • sortableAttributes for anything you’ll order by.
  await index.search('postgres', {
  filter: 'tags = database AND published = true',
  sort: ['published_at:desc'],
  limit: 20,
  attributesToHighlight: ['title'],
});
  

Keys, and the one that must not leak

Generate a search-only key for the browser and keep the master key on the server. It’s the same mistake as a database password in client code, and it’s easy to make because the quickstart hands you the master key first.

  const searchKey = await client.createKey({
  actions: ['search'],
  indexes: ['posts'],
  expiresAt: null,
});
  

Keeping the index in step with the database

Postgres stays the source of truth; the index is derived and disposable. Two patterns:

  • Reindex on write. After a create or update, push the document — ideally from a background job like Inngest so a slow index doesn’t slow the request.
  • Rebuild periodically. A nightly full reindex into a new index, then swap it, keeps drift from accumulating. Cheap, and it makes the “did the index miss something?” worry go away.

Choosing among the options

Fits
Postgres full-text searchModest data, no extra service — try tsvector first
MeilisearchInstant, typo-tolerant search you can self-host cheaply
TypesenseVery similar; pick on which API and hosting you prefer
AlgoliaDon’t want to run anything, and the bill is acceptable
Elasticsearch / OpenSearchLog analytics and complex aggregations, not just search

Genuinely start with Postgres. A tsvector column and a GIN index covers more products than people expect, and one fewer service to run is worth a lot. Move here when typo-tolerance, ranking, or search-as-you-type becomes a real requirement.

Next

Search tells you what people looked for; analytics tells you what they did → PostHog

Last updated 25 Aug 2026, 00:00 UTC. history