Open-source search engine, C++ core, native semantic + vector + geo search. Run as a single binary or hosted on Typesense Cloud. Algolia-like DX with no per-record fee.
Typesense's distinguishing features: native vector search (no separate vector DB), built-in geo search, opinionated about defaults (less config than Algolia, more than Meilisearch).
docker run -p 8108:8108 \
-v $(pwd)/typesense-data:/data \
-e TYPESENSE_API_KEY=your-strong-api-key \
-e TYPESENSE_DATA_DIR=/data \
typesense/typesense:latest \
--enable-cors
Listens on http://localhost:8108. Health check:
curl http://localhost:8108/health
# {"ok":true}
Single binary version (no Docker): install docs β.
This tutorial assumes Typesense Cloud or single-node self-hosted; Typesense supports HA clusters but that's a separate setup.
The bootstrap admin key (you set it at startup) is for backend admin operations. For app code, create scoped keys:
curl http://localhost:8108/keys \
-H 'X-TYPESENSE-API-KEY: your-strong-api-key' \
-H 'Content-Type: application/json' \
-d '{
"description": "Search-only key",
"actions": ["documents:search"],
"collections": ["*"]
}'
Copy the returned value β that's your client-side search key. Safe in browser. API keys docs β.
TYPESENSE_HOST=https://search.yourdomain.com
TYPESENSE_ADMIN_KEY=your-strong-api-key # backend only
TYPESENSE_SEARCH_KEY=xxxxxxxxxxxxxxxxxxxxx # safe in client
npm install typesense
Other languages: Python, Ruby, PHP, Go, Java, .NET, Swift, Dart β.
Unlike Algolia / Meilisearch (which infer schema from documents), Typesense expects an explicit schema:
import Typesense from "typesense";
const client = new Typesense.Client({
nodes: [{ host: "localhost", port: 8108, protocol: "http" }],
apiKey: process.env.TYPESENSE_ADMIN_KEY,
});
await client.collections().create({
name: "posts",
fields: [
{ name: "title", type: "string" },
{ name: "body", type: "string" },
{ name: "tags", type: "string[]", facet: true },
{ name: "published_at", type: "int64" },
],
default_sorting_field: "published_at",
});
Defining types upfront is more work than the auto-infer model β but you get better query performance and clear errors when data doesn't match. Collections docs β.
// Single document
await client.collections("posts").documents().create({
id: "post-123",
title: "How to deploy to Vercel",
body: "First, install the CLI...",
tags: ["deploy", "vercel"],
published_at: 1714521600,
});
// Bulk import (much faster for batches)
const jsonl = posts.map(p => JSON.stringify(p)).join("\n");
await client.collections("posts").documents().import(jsonl, { action: "upsert" });
JSONL bulk import handles 10K+ documents in a single request. Documents docs β.
// Backend or frontend (with search-only key)
const result = await client.collections("posts").documents().search({
q: "vercel deploy",
query_by: "title,body",
filter_by: 'tags:=["deploy"]',
sort_by: "published_at:desc",
per_page: 10,
});
console.log(result.hits);
The query_by parameter specifies which fields to search; filter_by adds Boolean filters; sort_by overrides default ranking.
Typesense ships native vector search β embeddings, similarity, hybrid search β without adding a separate vector DB.
await client.collections().create({
name: "articles",
fields: [
{ name: "title", type: "string" },
{ name: "embedding", type: "float[]", num_dim: 384 },
],
});
// Hybrid search: keyword + vector
await client.collections("articles").documents().search({
q: "deployment strategies",
query_by: "title,embedding",
vector_query: "embedding:([0.1, 0.2, ...], k:10)",
});
Combine keyword matching with semantic similarity in one query. Vector search docs β.
Typesense can also auto-generate embeddings via built-in models β pass embed config in the schema; Typesense fetches embeddings from OpenAI / Google PaLM / open-source models for you.
Built-in lat/lng support β find records within X km of a point:
// Schema
{ name: "location", type: "geopoint" }
// Document
{ location: [37.7749, -122.4194] }
// Search "within 5km of San Francisco"
await client.collections("places").documents().search({
q: "*",
filter_by: "location:(37.7749, -122.4194, 5 km)",
});
No separate geo DB; works alongside text search in the same query.
Typesense ships an Algolia-compatible InstantSearch adapter β same React/Vue widgets:
npm install typesense-instantsearch-adapter react-instantsearch
import TypesenseInstantsearchAdapter from "typesense-instantsearch-adapter";
const adapter = new TypesenseInstantsearchAdapter({
server: {
nodes: [{ host: "search.yourdomain.com", port: 443, protocol: "https" }],
apiKey: process.env.NEXT_PUBLIC_TYPESENSE_SEARCH_KEY,
},
additionalSearchParameters: { query_by: "title,body" },
});
import { InstantSearch, SearchBox, Hits } from "react-instantsearch";
export function Search() {
return (
<InstantSearch searchClient={adapter.searchClient} indexName="posts">
<SearchBox />
<Hits hitComponent={({ hit }) => <div>{hit.title}</div>} />
</InstantSearch>
);
}
Drop-in replacement for an Algolia searchClient. Adapter repo β.
Self-hosted: free; you pay for the VPS ($5β10/mo for most projects).
Typesense Cloud: starts ~$20/mo for a small cluster. Scales linearly with cluster size, not per-record. Pricing β.
At Algolia's $25K/year tier, Typesense Cloud would be roughly $2-3K β order of magnitude cheaper.