Getting Started
Run Meilisearch in Docker, index sample documents, query from a JS app, build an instant-search UI
Getting Started
Meilisearch is the friendliest place to start — open-source, single-binary, Algolia-like API. This page boots it locally, indexes data, and wires up a real instant-search UI.
The same patterns transfer to Typesense and Algolia with minimal changes.
Run Meilisearch
# docker-compose.yml
services:
meilisearch:
image: getmeili/meilisearch:v1.10
ports: ["7700:7700"]
environment:
MEILI_MASTER_KEY: "dev-master-key-change-me"
MEILI_ENV: development
volumes:
- meili-data:/meili_data
volumes:
meili-data:docker compose up -d
curl http://localhost:7700/health
# {"status":"available"}
open http://localhost:7700 # dashboard UI (dev only)Index Some Documents
# Create an index of "products"
curl -X POST 'http://localhost:7700/indexes' \
-H 'Authorization: Bearer dev-master-key-change-me' \
-H 'Content-Type: application/json' \
--data '{"uid": "products", "primaryKey": "id"}'
# Add documents
curl -X POST 'http://localhost:7700/indexes/products/documents' \
-H 'Authorization: Bearer dev-master-key-change-me' \
-H 'Content-Type: application/json' \
--data '[
{ "id": 1, "name": "Espresso Maker", "category": "kitchen", "price": 79, "in_stock": true },
{ "id": 2, "name": "French Press", "category": "kitchen", "price": 29, "in_stock": true },
{ "id": 3, "name": "Coffee Grinder", "category": "kitchen", "price": 49, "in_stock": false },
{ "id": 4, "name": "Reading Light", "category": "lighting", "price": 39, "in_stock": true }
]'Query It
# Basic search
curl -X POST 'http://localhost:7700/indexes/products/search' \
-H 'Authorization: Bearer dev-master-key-change-me' \
-H 'Content-Type: application/json' \
--data '{"q":"espreso"}' # note the typoThe response includes the espresso maker — Meilisearch handles typos out of the box.
Filters & Facets
Configure which attributes are filterable:
curl -X PATCH 'http://localhost:7700/indexes/products/settings' \
-H 'Authorization: Bearer dev-master-key-change-me' \
-H 'Content-Type: application/json' \
--data '{
"filterableAttributes": ["category", "in_stock", "price"],
"sortableAttributes": ["price"]
}'Then search with filters:
curl -X POST 'http://localhost:7700/indexes/products/search' \
-H 'Authorization: Bearer dev-master-key-change-me' \
--data '{
"q": "coffee",
"filter": "in_stock = true AND price < 50",
"sort": ["price:asc"],
"facets": ["category"]
}'The facets array returns counts per category — exactly what you'd render as filter chips in a UI.
Integrate From Node
npm install meilisearchimport { MeiliSearch } from 'meilisearch';
const client = new MeiliSearch({
host: 'http://localhost:7700',
apiKey: 'dev-master-key-change-me',
});
const index = client.index('products');
// Index a batch
await index.addDocuments([
{ id: 5, name: 'Kettle', category: 'kitchen', price: 25, in_stock: true },
]);
// Search
const result = await index.search('coffee', {
filter: 'in_stock = true',
sort: ['price:asc'],
limit: 10,
});
console.log(result.hits);Instant Search UI
The killer UX feature of dedicated search engines: instant results as you type, with filters, highlights, and infinite scroll.
npm install instantsearch.js @meilisearch/instant-meilisearchimport instantsearch from 'instantsearch.js';
import { instantMeiliSearch } from '@meilisearch/instant-meilisearch';
import { searchBox, hits, refinementList, configure } from 'instantsearch.js/es/widgets';
const { searchClient } = instantMeiliSearch(
'http://localhost:7700',
// For client-side use a SEARCH key, not the master key (see below)
'public-search-key',
);
const search = instantsearch({
indexName: 'products',
searchClient,
});
search.addWidgets([
searchBox({ container: '#searchbox' }),
configure({ hitsPerPage: 10 }),
hits({
container: '#hits',
templates: {
item: (item) => `
<div>
<h3>${item.name}</h3>
<p>${item.category} · $${item.price}</p>
</div>
`,
},
}),
refinementList({ container: '#category-filter', attribute: 'category' }),
]);
search.start();Stand up a simple HTML page with <div id="searchbox">, <div id="hits">, <div id="category-filter">, and you have a working production-grade search UI with maybe 30 lines of code.
API Keys: Master vs Search
Meilisearch (and Algolia, Typesense) use scoped keys:
| Key | What it can do | Where it goes |
|---|---|---|
| Master key | Everything (admin) | Backend only, never in the browser |
| Admin key | Index, configure | Backend / CI |
| Search key | Search only, scoped to specific indexes | Safe to ship to the browser |
Generate a search-only key:
curl -X POST 'http://localhost:7700/keys' \
-H 'Authorization: Bearer dev-master-key-change-me' \
-H 'Content-Type: application/json' \
--data '{
"description": "Public search key",
"actions": ["search"],
"indexes": ["products"],
"expiresAt": null
}'The response contains the key. Ship that to the browser, not the master key.
Tear Down
docker compose down -vThe Same Pattern, Other Engines
Typesense:
# Run Typesense
docker run -p 8108:8108 -v $(pwd)/typesense-data:/data \
typesense/typesense:0.26.0 \
--data-dir /data --api-key xyz123 --enable-cors
# Create a collection, add documents, search — same shape, different pathsAlgolia: sign up, get an Application ID and Admin API Key, install algoliasearch SDK, push documents, search. Same conceptual API.
What's Next
You can index and query. Next:
- Algolia vs Meilisearch — head-to-head comparison and selection guide
- Best Practices — indexing strategy, relevance tuning, secrets, observability