search-meilisearch
Meilisearch as the search provider for an Evolve shop. It has two parts that
deploy separately. The index adapter writes the catalog into Meilisearch, with
atomic full rebuilds, and the query adapter answers product search from it,
with facets, filters and sorting. Both plug into @evolve-framework/search, so
the indexing and the storefront's queries work the same as with any other
provider. Meilisearch is self-hosted, so choosing it puts no part of a catalog
behind a third party.
When to use
- A shop's product search and listings come from a Meilisearch you run.
- An indexer service keeps Meilisearch in step with the catalog: give its
SearchIndexerModuleorIndexSyncServiceaMeilisearchIndexAdapter. - You are moving from Algolia: Meilisearch sorts any index by any sortable attribute, so the replica per sort order goes away.
A service usually needs only one of the two: the job that fills the index does not answer queries, and the subgraph that answers queries does not write.
Get started
Install the package with the peer dependencies it needs. It comes from the Evolve registry, which needs a one-time sign-in.
pnpm add @evolve-framework/search-meilisearch @evolve-framework/searchRequirements2
@evolve-framework/search>= 0.2.0 < 2.0.0@types/node>=22optional
For example:
import { MeilisearchIndexAdapter } from "@evolve-framework/search-meilisearch/indexing";import { MeilisearchQueryAdapter } from "@evolve-framework/search-meilisearch/query";
const credentials = { host: process.env.MEILISEARCH_URL ?? "http://localhost:7700", apiKey: process.env.MEILISEARCH_API_KEY ?? "",};
// The indexer: only the attributes named here become facets.export const indexAdapter = new MeilisearchIndexAdapter({ ...credentials, facetableAttributes: ["brand", "colour", "size"],});
// The service answering product search.export const queryAdapter = new MeilisearchQueryAdapter(credentials);Adapters
MeilisearchIndexAdapter
Meilisearch as an index adapter.
One index per store and locale. beginFullReindex builds into a temporary
index and swaps it in, which Meilisearch does atomically — so a rebuild never
serves a half-filled index, and a failed rebuild leaves the live one
untouched. That is the capability Algolia needs a temporary index and a move
for, and it is the reason a full resync here is safe to run against
production.
import { MeilisearchIndexAdapter } from "@evolve-framework/search-meilisearch";new MeilisearchIndexAdapter(options: MeilisearchIndexAdapterOptions)| Option | Type | Description |
|---|---|---|
host | string | The Meilisearch URL, for example |
apiKey | string | Held server-side only; a browser must never get it. |
resolveIndexName? | IndexNameResolver | Defaults to |
batchSize? | number | Documents per request. Meilisearch has no hard cap; this bounds memory. |
toRecord? | ((document: ProductDocument, locale: string) => MeilisearchProductRecord) | Inverse of the query adapter's reader. Defaults to |
filterableAttributes? | string[] | Attributes that can be filtered and facetted on, beyond the ones every product document has. A project's own attribute names go here. |
facetableAttributes? | string[] | Which product attributes to offer as facets, by their own names —
MoreThis is the same lever Algolia calls Curated rather than open. A wildcard works — and a real catalogue then
returns 224 attribute facets on one page, including a raw id, a
merchandiser's duplicate named Index-wide, because that is as far as Meilisearch's settings reach. Per category needs a layer above this one — Algolia does that with Rules, and Meilisearch has no equivalent. |
sortableAttributes? | string[] | Attributes that can be sorted on, beyond the price fields. |
swapTimeoutMs? | number | How long to wait for the index swap that ends a full rebuild. MoreThe client's own default is five seconds, which is a rebuild of a small catalogue. A swap covering eight locale indexes of several thousand documents takes longer, and the wait then times out on work that goes on to succeed — so a reindex that worked reports failure, and a scheduled one alerts on nothing. |
| Method | Description |
|---|---|
init(): Promise<void> | One-shot, idempotent initialization (build SDK clients, etc.). |
upsertDocuments(
scope: IndexScope,
documents: ProductDocument[],
): Promise<void> | Write documents into the scope's target for each one's type, replacing any stored document with the same id as a whole. |
deleteDocuments(
scope: IndexScope,
ids: string[],
documentTypes: string[],
): Promise<void> | Remove documents by id, from the target of every type in documentTypes. |
deleteBySource(
scope: IndexScope,
sourceIds: string[],
options: { documentTypes: string[]; keep?: string[] | undefined; },
): Promise<void> | Remove everything that came from a source, except what is still current. |
MeilisearchQueryAdapter
Answers product search from Meilisearch, with facets, filters and sorting by relevance, price, name, review rating or newest, reading the index per store and locale that MeilisearchIndexAdapter fills.
import { MeilisearchQueryAdapter } from "@evolve-framework/search-meilisearch";new MeilisearchQueryAdapter(options: MeilisearchQueryAdapterOptions)| Option | Type | Description |
|---|---|---|
host | string | The Meilisearch URL, for example |
apiKey | string | Held server-side only; a browser must never get it. |
resolveIndexName? | IndexNameResolver | Defaults to |
facets? | string[] | Attributes to return facet counts for. |
distinct? | string | null | The field results are grouped by, one hit per distinct value. Defaults to
|
toDocument? | ((record: MeilisearchProductRecord, locale: string) => ProductDocument) | Inverse of the index adapter's |
maxTotalHits? | number | The index's |
| Method | Description |
|---|---|
init(): Promise<void> | Create the Meilisearch client from host and apiKey. |
search(
query: ProductSearchQuery,
): Promise<SearchResponse<ProductDocument>> | Run a query and return the documents that matched. |
Entry points
@evolve-framework/search-meilisearch | Both adapters, the record shape, defaultIndexName, and the mappers toRecord and toProductDocument8 exports |
@evolve-framework/search-meilisearch/indexing | MeilisearchIndexAdapter1 export |
@evolve-framework/search-meilisearch/query | MeilisearchQueryAdapter, plus buildFilter and mapFacets for a query adapter of your own6 exports |

