Meilisearch vs Typesense vs Algolia: Search Engine APIs (2026)
TL;DR
Meilisearch is the open-source search engine — instant search, typo tolerance, faceted filtering, easy setup, Rust-powered, great developer experience. Typesense is the open-source search engine built for speed — typo tolerance, geo search, vector search, high availability, also Rust/C++. Algolia is the hosted search-as-a-service — instant search UI widgets, analytics, A/B testing, AI search, the industry standard for commercial search. In 2026: Meilisearch for easy self-hosted search, Typesense for fast open-source search with vector support, Algolia for enterprise search-as-a-service.
Key Takeaways
- Meilisearch: meilisearch ~50K weekly downloads — open-source, Rust, instant search, easy setup
- Typesense: typesense ~20K weekly downloads — open-source, fast, vector search, geo search
- Algolia: algoliasearch ~500K weekly downloads — hosted, analytics, AI, InstantSearch widgets
- Meilisearch and Typesense are open-source (self-hosted or cloud)
- Algolia has the richest UI component library (InstantSearch)
- All three handle typo tolerance out of the box
Meilisearch
Meilisearch — open-source instant search:
Setup and indexing
import { MeiliSearch } from "meilisearch"
const client = new MeiliSearch({
host: "http://localhost:7700",
apiKey: process.env.MEILI_MASTER_KEY,
})
// Create index and add documents:
const index = client.index("packages")
await index.addDocuments([
{ id: 1, name: "react", description: "UI library", downloads: 25000000, tags: ["frontend", "ui"] },
{ id: 2, name: "vue", description: "Progressive framework", downloads: 5000000, tags: ["frontend", "ui"] },
{ id: 3, name: "express", description: "Web framework", downloads: 30000000, tags: ["backend", "http"] },
{ id: 4, name: "fastify", description: "Fast web framework", downloads: 5000000, tags: ["backend", "http"] },
])
// Configure searchable attributes and ranking:
await index.updateSettings({
searchableAttributes: ["name", "description", "tags"],
filterableAttributes: ["tags", "downloads"],
sortableAttributes: ["downloads", "name"],
rankingRules: [
"words", "typo", "proximity", "attribute", "sort", "exactness",
],
})
Search
const index = client.index("packages")
// Basic search (typo-tolerant by default):
const results = await index.search("reac")
// Finds "react" despite typo
console.log(results.hits)
// [{ id: 1, name: "react", ... }]
console.log(results.estimatedTotalHits)
console.log(results.processingTimeMs) // ~1ms
// With filters:
const filtered = await index.search("framework", {
filter: ["tags = frontend", "downloads > 1000000"],
sort: ["downloads:desc"],
limit: 10,
offset: 0,
})
// Faceted search:
const faceted = await index.search("", {
facets: ["tags"],
})
console.log(faceted.facetDistribution)
// { tags: { frontend: 2, backend: 2, ui: 2, http: 2 } }
Highlighted results
const results = await index.search("web framework", {
attributesToHighlight: ["name", "description"],
highlightPreTag: "<mark>",
highlightPostTag: "</mark>",
attributesToCrop: ["description"],
cropLength: 30,
})
results.hits.forEach((hit) => {
console.log(hit._formatted?.name)
// "express" or "<mark>web</mark> <mark>framework</mark>"
console.log(hit._formatted?.description)
// "<mark>Web</mark> <mark>framework</mark>"
})
React InstantSearch
import { InstantSearch, SearchBox, Hits, RefinementList } from "react-instantsearch"
import { instantMeiliSearch } from "@meilisearch/instant-meilisearch"
const { searchClient } = instantMeiliSearch(
"http://localhost:7700",
process.env.MEILI_SEARCH_KEY
)
function PackageSearch() {
return (
<InstantSearch searchClient={searchClient} indexName="packages">
<SearchBox placeholder="Search packages..." />
<RefinementList attribute="tags" />
<Hits hitComponent={PackageHit} />
</InstantSearch>
)
}
function PackageHit({ hit }: { hit: any }) {
return (
<div>
<h3>{hit.name}</h3>
<p>{hit.description}</p>
<span>{hit.downloads.toLocaleString()} downloads/week</span>
</div>
)
}
Typesense
Typesense — fast open-source search:
Setup and indexing
import Typesense from "typesense"
const client = new Typesense.Client({
nodes: [{ host: "localhost", port: 8108, protocol: "http" }],
apiKey: process.env.TYPESENSE_API_KEY!,
connectionTimeoutSeconds: 2,
})
// Create collection with schema:
await client.collections().create({
name: "packages",
fields: [
{ name: "name", type: "string" },
{ name: "description", type: "string" },
{ name: "downloads", type: "int64" },
{ name: "tags", type: "string[]", facet: true },
{ name: "version", type: "string" },
],
default_sorting_field: "downloads",
})
// Index documents:
await client.collections("packages").documents().import([
{ name: "react", description: "UI library", downloads: 25000000, tags: ["frontend", "ui"], version: "19.0.0" },
{ name: "vue", description: "Progressive framework", downloads: 5000000, tags: ["frontend", "ui"], version: "3.5.0" },
{ name: "express", description: "Web framework", downloads: 30000000, tags: ["backend", "http"], version: "5.0.0" },
])
Search
// Basic search:
const results = await client.collections("packages")
.documents()
.search({
q: "reac", // Typo-tolerant
query_by: "name,description",
sort_by: "downloads:desc",
per_page: 10,
page: 1,
})
console.log(results.found) // Total matches
console.log(results.search_time_ms) // ~0.5ms
results.hits?.forEach((hit) => {
console.log(hit.document.name, hit.text_match)
})
// Filtered search:
const filtered = await client.collections("packages")
.documents()
.search({
q: "framework",
query_by: "name,description",
filter_by: "tags:=[frontend] && downloads:>1000000",
sort_by: "downloads:desc",
facet_by: "tags",
})
console.log(filtered.facet_counts)
// [{ field_name: "tags", counts: [{ value: "frontend", count: 2 }] }]
Vector search (semantic)
// Create collection with vector field:
await client.collections().create({
name: "packages-semantic",
fields: [
{ name: "name", type: "string" },
{ name: "description", type: "string" },
{ name: "embedding", type: "float[]", num_dim: 384 },
],
})
// Index with embeddings:
await client.collections("packages-semantic").documents().import([
{
name: "react",
description: "A JavaScript library for building user interfaces",
embedding: [0.1, 0.2, ...], // 384-dim vector
},
])
// Vector search:
const results = await client.collections("packages-semantic")
.documents()
.search({
q: "*",
vector_query: "embedding:([0.1, 0.2, ...], k:10)",
})
// Hybrid search (keyword + vector):
const hybrid = await client.collections("packages-semantic")
.documents()
.search({
q: "UI library",
query_by: "name,description",
vector_query: "embedding:([0.1, 0.2, ...], k:10, alpha:0.5)",
})
Geo search
// Collection with geo field:
await client.collections().create({
name: "events",
fields: [
{ name: "name", type: "string" },
{ name: "location", type: "geopoint" },
{ name: "date", type: "string" },
],
})
// Search within radius:
const nearby = await client.collections("events")
.documents()
.search({
q: "javascript meetup",
query_by: "name",
filter_by: "location:(37.7749, -122.4194, 10 km)", // 10km from SF
sort_by: "location(37.7749, -122.4194):asc", // Nearest first
})
Algolia
Algolia — search-as-a-service:
Setup and indexing
import algoliasearch from "algoliasearch"
const client = algoliasearch(
process.env.ALGOLIA_APP_ID!,
process.env.ALGOLIA_ADMIN_KEY!
)
const index = client.initIndex("packages")
// Index documents:
await index.saveObjects([
{ objectID: "1", name: "react", description: "UI library", downloads: 25000000, tags: ["frontend", "ui"] },
{ objectID: "2", name: "vue", description: "Progressive framework", downloads: 5000000, tags: ["frontend", "ui"] },
{ objectID: "3", name: "express", description: "Web framework", downloads: 30000000, tags: ["backend", "http"] },
])
// Configure index settings:
await index.setSettings({
searchableAttributes: ["name", "description", "tags"],
attributesForFaceting: ["tags", "filterOnly(downloads)"],
customRanking: ["desc(downloads)"],
typoTolerance: true,
minWordSizefor1Typo: 3,
minWordSizefor2Typos: 7,
})
Search
const searchClient = algoliasearch(
process.env.ALGOLIA_APP_ID!,
process.env.ALGOLIA_SEARCH_KEY! // Search-only key
)
const index = searchClient.initIndex("packages")
// Basic search:
const { hits, nbHits, processingTimeMS } = await index.search("reac")
console.log(nbHits) // Total matches
console.log(processingTimeMS) // ~1ms
hits.forEach((hit) => {
console.log(hit.name)
console.log(hit._highlightResult?.name?.value)
// "<em>reac</em>t" — auto-highlighted
})
// Filtered search:
const filtered = await index.search("framework", {
filters: "tags:frontend AND downloads > 1000000",
facets: ["tags"],
hitsPerPage: 10,
page: 0,
})
console.log(filtered.facets)
// { tags: { frontend: 2, backend: 2 } }
React InstantSearch
import algoliasearch from "algoliasearch/lite"
import {
InstantSearch, SearchBox, Hits,
RefinementList, Pagination, Stats,
} from "react-instantsearch"
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!
)
function PackageSearch() {
return (
<InstantSearch searchClient={searchClient} indexName="packages">
<div className="flex gap-8">
<aside className="w-64">
<h3>Tags</h3>
<RefinementList attribute="tags" />
</aside>
<main className="flex-1">
<SearchBox placeholder="Search packages..." />
<Stats />
<Hits hitComponent={PackageHit} />
<Pagination />
</main>
</div>
</InstantSearch>
)
}
function PackageHit({ hit }: { hit: any }) {
return (
<article>
<h3>{hit.name}</h3>
<p>{hit.description}</p>
<div className="flex gap-2">
{hit.tags?.map((tag: string) => (
<span key={tag} className="badge">{tag}</span>
))}
</div>
</article>
)
}
Analytics and A/B testing
import algoliasearch from "algoliasearch"
const client = algoliasearch(appId, adminKey)
// Enable analytics:
const index = client.initIndex("packages")
await index.setSettings({
enablePersonalization: true,
enableRules: true,
})
// Query rules (merchandising):
await index.saveRule({
objectID: "promote-react",
conditions: [{
anchoring: "contains",
pattern: "ui library",
}],
consequence: {
promote: [{
objectID: "1", // react
position: 0,
}],
},
})
// A/B test:
const response = await client.addABTest({
name: "ranking-test",
variants: [
{ index: "packages", trafficPercentage: 50, description: "Current" },
{ index: "packages_v2", trafficPercentage: 50, description: "New ranking" },
],
endAt: "2026-04-01T00:00:00Z",
})
Feature Comparison
| Feature | Meilisearch | Typesense | Algolia |
|---|---|---|---|
| Open-source | ✅ | ✅ | ❌ (proprietary) |
| Self-hosted | ✅ | ✅ | ❌ (cloud only) |
| Cloud offering | Meilisearch Cloud | Typesense Cloud | ✅ (primary) |
| Typo tolerance | ✅ | ✅ | ✅ |
| Faceted search | ✅ | ✅ | ✅ |
| Geo search | ✅ | ✅ | ✅ |
| Vector search | ✅ (experimental) | ✅ | ✅ (NeuralSearch) |
| InstantSearch UI | ✅ (via adapter) | ✅ (via adapter) | ✅ (native) |
| Analytics | ❌ | ❌ | ✅ |
| A/B testing | ❌ | ❌ | ✅ |
| Query rules | ❌ | ✅ (overrides) | ✅ |
| Multi-tenancy | ✅ (tenant tokens) | ✅ (scoped keys) | ✅ (API keys) |
| Written in | Rust | C++ | C++ |
| Indexing speed | Fast | Very fast | Fast |
| Search latency | ~1-5ms | ~0.5-2ms | ~1-5ms |
| Free tier | Self-hosted | Self-hosted | 10K searches/mo |
When to Use Each
Use Meilisearch if:
- Want easy-to-setup open-source search
- Need instant search with great developer experience
- Building search for small to medium datasets
- Want self-hosted search with minimal configuration
Use Typesense if:
- Need the fastest open-source search engine
- Want vector search for semantic/AI-powered search
- Need geo search capabilities
- Building search for large datasets with strict latency requirements
Use Algolia if:
- Want fully managed search-as-a-service
- Need analytics, A/B testing, and query rules
- Building commercial search for e-commerce or SaaS
- Want the richest InstantSearch UI component library
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on Meilisearch v1.x, Typesense v0.25.x/v27.x, and Algolia v4.x/v5.x.