Algolia
"Algolia: instant search, faceted search, InstantSearch.js/React, indexing, ranking, search analytics"
Algolia is a hosted search-as-a-service platform built for speed and relevance. Its core tenets are: ## Key Points - **Sub-50ms responses** — search results return in milliseconds regardless of dataset size. - **Typo tolerance by default** — users get results even with misspellings. - **Relevance out of the box** — tie-breaking ranking criteria (textual, then custom) ensure the best results surface first. - **Frontend-first architecture** — queries go directly from the browser to Algolia's API, bypassing your backend for search reads. - **Configuration over code** — ranking, synonyms, and rules are configured declaratively, not through custom scoring logic. - **Use the search-only API key on the frontend.** Never expose the admin key in client code. Generate scoped keys for multi-tenant apps with `client.generateSecuredApiKey()`. - **Push full records, not deltas.** When reindexing, use `replaceAllObjects` for zero-downtime atomic swaps of the entire index. - **Keep records small.** Only index fields you search or filter on. Store large blobs elsewhere and reference them by ID. - **Set searchableAttributes explicitly.** Without this, Algolia searches all fields, diluting relevance. - **Use `filterOnly` for attributes you filter but never display as facets.** This saves processing. - **Leverage query rules** for merchandising instead of hardcoding promotions in application logic. - **Send analytics events** (clicks, conversions) to enable Algolia's AI re-ranking and Recommend features.
skilldb get search-services-skills/AlgoliaFull skill: 257 linesAlgolia
Core Philosophy
Algolia is a hosted search-as-a-service platform built for speed and relevance. Its core tenets are:
- Sub-50ms responses — search results return in milliseconds regardless of dataset size.
- Typo tolerance by default — users get results even with misspellings.
- Relevance out of the box — tie-breaking ranking criteria (textual, then custom) ensure the best results surface first.
- Frontend-first architecture — queries go directly from the browser to Algolia's API, bypassing your backend for search reads.
- Configuration over code — ranking, synonyms, and rules are configured declaratively, not through custom scoring logic.
Treat Algolia as the read layer for search. Your backend pushes data into indices; your frontend queries them directly using API keys with restricted permissions.
Setup
Install the JavaScript API client and the React InstantSearch library:
// npm install algoliasearch react-instantsearch
import algoliasearch from "algoliasearch";
const client = algoliasearch("YOUR_APP_ID", "YOUR_ADMIN_API_KEY");
const index = client.initIndex("products");
// Push records to the index
async function indexProducts(products: Product[]) {
// Every record needs a unique objectID
const records = products.map((p) => ({
objectID: p.id,
name: p.name,
description: p.description,
price: p.price,
categories: p.categories,
rating: p.rating,
inStock: p.inStock,
}));
const { objectIDs } = await index.saveObjects(records);
console.log(`Indexed ${objectIDs.length} records`);
}
Configure index settings for ranking and faceting:
async function configureIndex() {
await index.setSettings({
// Attributes users can search against
searchableAttributes: [
"name", // highest priority
"description", // second priority
"categories", // third priority
],
// Attributes available for faceted filtering
attributesForFaceting: [
"categories",
"filterOnly(inStock)", // filter only, not displayed as facet
"searchable(brand)", // facet that is also searchable
],
// Custom ranking after textual relevance
customRanking: [
"desc(rating)",
"desc(salesCount)",
],
// Attributes returned in results
attributesToRetrieve: ["name", "description", "price", "image"],
attributesToHighlight: ["name", "description"],
});
}
Key Techniques
Direct Backend Search
async function searchProducts(query: string, filters?: string) {
const { hits, nbHits, processingTimeMS } = await index.search<Product>(query, {
filters: filters ?? "",
hitsPerPage: 20,
page: 0,
attributesToRetrieve: ["name", "price", "image"],
attributesToHighlight: ["name"],
});
console.log(`Found ${nbHits} results in ${processingTimeMS}ms`);
return hits;
}
// Faceted search
async function searchWithFacets(query: string, category?: string) {
return index.search<Product>(query, {
facets: ["categories", "brand"],
filters: category ? `categories:"${category}"` : "",
});
}
React InstantSearch Integration
import React from "react";
import algoliasearch from "algoliasearch/lite"; // lightweight search-only client
import {
InstantSearch,
SearchBox,
Hits,
RefinementList,
Pagination,
Highlight,
Configure,
useSearchBox,
useHits,
} from "react-instantsearch";
const searchClient = algoliasearch("YOUR_APP_ID", "YOUR_SEARCH_ONLY_KEY");
function ProductHit({ hit }: { hit: Product }) {
return (
<article>
<h3>
<Highlight attribute="name" hit={hit} />
</h3>
<p>
<Highlight attribute="description" hit={hit} />
</p>
<span>${hit.price}</span>
</article>
);
}
function SearchPage() {
return (
<InstantSearch searchClient={searchClient} indexName="products">
<Configure hitsPerPage={20} />
<div className="search-layout">
<aside>
<h4>Categories</h4>
<RefinementList attribute="categories" />
</aside>
<main>
<SearchBox placeholder="Search products..." />
<Hits hitComponent={ProductHit} />
<Pagination />
</main>
</div>
</InstantSearch>
);
}
Synonyms and Rules
async function configureSynonyms() {
await index.saveSynonyms([
{
objectID: "phone-synonyms",
type: "synonym",
synonyms: ["phone", "mobile", "cell", "smartphone"],
},
{
objectID: "tv-oneway",
type: "oneWaySynonym",
input: "tv",
synonyms: ["television", "flat screen"],
},
]);
}
// Query rules — pin results or modify ranking for specific queries
async function configureRules() {
await index.saveRule({
objectID: "promo-banner",
conditions: [{ anchoring: "contains", pattern: "sale" }],
consequence: {
userData: { banner: "Summer sale — 20% off everything!" },
promote: [{ objectID: "featured-sale-item", position: 0 }],
},
});
}
Incremental Updates and Partial Updates
async function partialUpdate(productId: string, updates: Partial<Product>) {
await index.partialUpdateObject({
objectID: productId,
...updates,
});
}
// Batch partial updates for price changes
async function bulkPriceUpdate(priceMap: Map<string, number>) {
const updates = Array.from(priceMap.entries()).map(([id, price]) => ({
objectID: id,
price,
}));
await index.partialUpdateObjects(updates);
}
Search Analytics
// Send click and conversion events for AI re-ranking (Algolia Recommend)
import aa from "search-insights";
aa("init", { appId: "YOUR_APP_ID", apiKey: "YOUR_SEARCH_ONLY_KEY" });
function trackClick(queryID: string, objectID: string, position: number) {
aa("clickedObjectIDsAfterSearch", {
index: "products",
eventName: "Product Clicked",
queryID,
objectIDs: [objectID],
positions: [position],
});
}
function trackConversion(queryID: string, objectID: string) {
aa("convertedObjectIDsAfterSearch", {
index: "products",
eventName: "Product Purchased",
queryID,
objectIDs: [objectID],
});
}
Best Practices
- Use the search-only API key on the frontend. Never expose the admin key in client code. Generate scoped keys for multi-tenant apps with
client.generateSecuredApiKey(). - Push full records, not deltas. When reindexing, use
replaceAllObjectsfor zero-downtime atomic swaps of the entire index. - Keep records small. Only index fields you search or filter on. Store large blobs elsewhere and reference them by ID.
- Set searchableAttributes explicitly. Without this, Algolia searches all fields, diluting relevance.
- Use
filterOnlyfor attributes you filter but never display as facets. This saves processing. - Leverage query rules for merchandising instead of hardcoding promotions in application logic.
- Send analytics events (clicks, conversions) to enable Algolia's AI re-ranking and Recommend features.
Anti-Patterns
- Using Algolia as a primary database. It is a search index. Always keep a source-of-truth datastore and sync to Algolia.
- Proxying every search through your backend. This adds latency. Let the frontend query Algolia directly with a search-only key.
- Indexing HTML or markdown. Strip markup before indexing; raw tags hurt relevance and highlighting.
- Ignoring
attributesToRetrieve. Returning all fields wastes bandwidth. Specify only what the frontend renders. - Over-relying on
filtersfor access control. Use secured API keys with embedded filters for multi-tenant isolation instead of trusting client-side filter parameters. - Updating single records in a loop. Use
saveObjectsorpartialUpdateObjectsin batches for bulk changes.
Install this skill directly: skilldb add search-services-skills
Related Skills
Elasticsearch
"Elasticsearch: full-text search, aggregations, mapping, bulk indexing, Node.js client, relevance tuning"
Fuse Js
Fuse.js is a lightweight, powerful fuzzy-search library for JavaScript that runs entirely client-side. It's ideal for quickly adding flexible, typo-tolerant search capabilities to web applications without server-side infrastructure.
Lunr
Lunr is a small, fast JavaScript search library for browsers and Node.js. It allows you to build a search index directly within your application, providing full-text search capabilities without a backend API or external service. It's ideal for static sites, documentation, or client-side applications requiring offline-capable search.
Manticore Search
"Manticore Search: open-source full-text search, SQL-based queries, real-time indexes, columnar storage, Elasticsearch-compatible API"
Meilisearch
"Meilisearch: self-hosted search engine, typo tolerance, faceting, filtering, sorting, REST API, JavaScript SDK"
Opensearch
OpenSearch is a community-driven, open-source search and analytics suite derived from Elasticsearch. It's ideal for powering full-text search, log analytics, security monitoring, and real-time application monitoring, offering powerful scalability and flexibility for diverse data needs.