Meilisearch is a fast, open-source search engine that delivers instant, typo-tolerant search with minimal setup โ a simpler alternative to Elasticsearch for most applications. This guide adds powerful search to a web app from installation to frontend integration.
๐ Table of Contents
Why Meilisearch?
- Instant search: Sub-50ms responses, results as you type
- Typo tolerance: Finds results despite misspellings out of the box
- Simple setup: Far easier than Elasticsearch โ sensible defaults
- Open source: Self-hostable and free, with a cloud option
- Great relevance: Good results without complex tuning
Running Meilisearch
# Run with Docker (easiest)
docker run -d --name meilisearch -p 7700:7700 \
-e MEILI_MASTER_KEY="your-master-key" \
getmeili/meilisearch:latest
# Verify it's running
curl http://localhost:7700/health
# {"status":"available"}
Indexing Data
npm install meilisearch
import { MeiliSearch } from 'meilisearch';
const client = new MeiliSearch({
host: 'http://localhost:7700',
apiKey: 'your-master-key',
});
// Create an index and add documents
const index = client.index('products');
await index.addDocuments([
{ id: 1, name: 'Wireless Keyboard', category: 'Peripherals', price: 89 },
{ id: 2, name: 'Mechanical Keyboard', category: 'Peripherals', price: 129 },
{ id: 3, name: 'USB-C Hub', category: 'Accessories', price: 45 },
]);
// Each document needs a unique 'id' field (or configure the primary key)
Basic Search
// Simple search - typo-tolerant automatically
const results = await index.search('keybord'); // note the typo
console.log(results.hits);
// Still finds both keyboards despite the misspelling
// Search with options
const results = await index.search('keyboard', {
limit: 20,
attributesToHighlight: ['name'], // highlight matches
attributesToRetrieve: ['id', 'name', 'price'],
});
Configuring Searchable and Filterable Fields
// Set which fields are searchable (and their priority order)
await index.updateSearchableAttributes([
'name', // highest priority
'category',
'description',
]);
// Set filterable attributes for faceted filtering
await index.updateFilterableAttributes(['category', 'price']);
// Set sortable attributes
await index.updateSortableAttributes(['price']);
Filtering and Faceting
// Filter results
const results = await index.search('keyboard', {
filter: 'category = "Peripherals" AND price < 100',
});
// Faceted search - get counts per category
const results = await index.search('keyboard', {
facets: ['category'],
});
console.log(results.facetDistribution);
// { category: { Peripherals: 2, Accessories: 0 } }
// Sort results
const results = await index.search('keyboard', {
sort: ['price:asc'],
});
Frontend Integration (Instant Search)
// React example - search as you type
import { useState, useEffect } from 'react';
import { MeiliSearch } from 'meilisearch';
// Use a SEARCH-ONLY key on the frontend, never the master key
const client = new MeiliSearch({
host: 'https://search.example.com',
apiKey: 'search-only-key', // scoped key with search-only permissions
});
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) { setResults([]); return; }
const timer = setTimeout(async () => {
const res = await client.index('products').search(query, { limit: 10 });
setResults(res.hits);
}, 150); // debounce - avoid a request on every keystroke
return () => clearTimeout(timer);
}, [query]);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search..." />
<ul>
{results.map(hit => <li key={hit.id}>{hit.name}</li>)}
</ul>
</div>
);
}
Keeping the Index in Sync
// Update the index when your data changes
async function onProductCreated(product) {
await index.addDocuments([product]);
}
async function onProductUpdated(product) {
await index.updateDocuments([product]); // upserts by id
}
async function onProductDeleted(productId) {
await index.deleteDocument(productId);
}
// Call these from your app's create/update/delete handlers to keep
// search results current with your database.
Security: Use Scoped API Keys
// NEVER expose the master key to the frontend.
// Create a search-only key for client use:
const searchKey = await client.createKey({
actions: ['search'],
indexes: ['products'],
expiresAt: null,
});
// Use searchKey.key on the frontend - it can only search, not modify
Frequently Asked Questions
Q: Meilisearch or Elasticsearch?
A: Meilisearch for simplicity, instant search, and typo tolerance with minimal setup โ great for most apps. Elasticsearch for very large scale, complex analytics, and advanced features. For adding search to a typical web app, Meilisearch is far simpler and sufficient.
Q: Meilisearch or PostgreSQL full-text search?
A: PostgreSQL search avoids extra infrastructure (uses your existing database) and handles many apps well. Meilisearch offers better typo tolerance, instant search UX, and faceting out of the box, at the cost of running a separate service and syncing data. Choose based on whether search UX quality justifies the extra service.
Q: How do I keep the search index in sync with my database?
A: Update the Meilisearch index whenever your data changes โ call addDocuments/updateDocuments/deleteDocument from your app's create/update/delete handlers. For bulk sync, periodically re-index. The index is separate from your database, so you must keep them in sync.
Q: Is it safe to search from the frontend?
A: Yes, if you use a scoped search-only API key (never the master key). Create a key with only the 'search' action for specific indexes, and use that on the frontend. The master key must stay server-side only, as it can modify data.
Q: Can Meilisearch handle large datasets?
A: It handles millions of documents well with good performance. For extremely large scale (hundreds of millions) or complex analytics, Elasticsearch may be more appropriate. For most web apps โ e-commerce, content, SaaS โ Meilisearch scales more than adequately.
Conclusion
Meilisearch makes adding fast, typo-tolerant search to a web app remarkably simple โ far easier than Elasticsearch for most needs. The setup: run Meilisearch (Docker), index your documents, configure searchable and filterable attributes, and integrate instant search on the frontend with a debounced input and a scoped search-only API key. Keep the index in sync by updating it from your app's data change handlers. You get sub-50ms search, typo tolerance, faceting, and filtering out of the box with minimal configuration. For e-commerce, content sites, and most SaaS apps, Meilisearch delivers an excellent search experience without the complexity of heavier search infrastructure โ a great choice when you want quality search UX quickly.
๐ You might also like
๐ Share this article




โ๏ธ Leave a Comment