Skillshub algolia-sdk-patterns
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/algolia-sdk-patterns" ~/.claude/skills/comeonoliver-skillshub-algolia-sdk-patterns && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/algolia-sdk-patterns/SKILL.mdsource content
Algolia SDK Patterns
Overview
Production-ready patterns for
algoliasearch v5. Key architectural change from v4: all methods live on the client directly — no more client.initIndex(). Index name is passed as a parameter to every call.
Prerequisites
v5+ installedalgoliasearch- Completed
setupalgolia-install-auth - TypeScript project (patterns work in JS too, you just lose type safety)
Instructions
Pattern 1: Typed Singleton Client
// src/algolia/client.ts import { algoliasearch, type Algoliasearch } from 'algoliasearch'; let _client: Algoliasearch | null = null; export function getClient(): Algoliasearch { if (!_client) { const appId = process.env.ALGOLIA_APP_ID; const apiKey = process.env.ALGOLIA_ADMIN_KEY; if (!appId || !apiKey) { throw new Error( 'ALGOLIA_APP_ID and ALGOLIA_ADMIN_KEY must be set. ' + 'Get them from dashboard.algolia.com > Settings > API Keys' ); } _client = algoliasearch(appId, apiKey); } return _client; } // For testing: reset singleton export function resetClient(): void { _client = null; }
Pattern 2: Typed Search Results
// src/algolia/types.ts // Define your record shape — extends Algolia's Hit type interface Product { objectID: string; name: string; category: string; price: number; description: string; image_url: string; } // src/algolia/search.ts import { getClient } from './client'; export async function searchProducts( query: string, options?: { filters?: string; facetFilters?: string[][]; hitsPerPage?: number; page?: number; } ) { const client = getClient(); const { hits, nbHits, nbPages, page } = await client.searchSingleIndex<Product>({ indexName: 'products', searchParams: { query, filters: options?.filters, facetFilters: options?.facetFilters, hitsPerPage: options?.hitsPerPage ?? 20, page: options?.page ?? 0, attributesToRetrieve: ['name', 'category', 'price', 'image_url'], attributesToHighlight: ['name', 'description'], }, }); return { hits, totalHits: nbHits, totalPages: nbPages, currentPage: page }; } // Usage: const { hits } = await searchProducts('laptop', { filters: 'price < 1000' });
Pattern 3: Error Handling with Algolia Error Types
// src/algolia/errors.ts import { ApiError } from 'algoliasearch'; export async function safeAlgoliaCall<T>( operation: string, fn: () => Promise<T> ): Promise<{ data: T | null; error: string | null }> { try { const data = await fn(); return { data, error: null }; } catch (err) { if (err instanceof ApiError) { // ApiError has status and message from Algolia API const msg = `Algolia ${operation} failed [${err.status}]: ${err.message}`; console.error(msg); // Specific handling for common codes if (err.status === 429) { console.warn('Rate limited — reduce request frequency or contact Algolia'); } else if (err.status === 404) { console.warn('Index or object not found — verify index name'); } return { data: null, error: msg }; } // Non-Algolia error (network, etc.) const msg = err instanceof Error ? err.message : 'Unknown error'; console.error(`${operation} error: ${msg}`); return { data: null, error: msg }; } } // Usage: // const { data, error } = await safeAlgoliaCall('search', () => // client.searchSingleIndex({ indexName: 'products', searchParams: { query: 'foo' } }) // );
Pattern 4: Batch Operations
// src/algolia/batch.ts import { getClient } from './client'; // saveObjects handles batching internally — send up to 1000 objects per call export async function bulkIndex(indexName: string, records: Record<string, any>[]) { const client = getClient(); const BATCH_SIZE = 1000; for (let i = 0; i < records.length; i += BATCH_SIZE) { const batch = records.slice(i, i + BATCH_SIZE); const { taskID } = await client.saveObjects({ indexName, objects: batch, }); await client.waitForTask({ indexName, taskID }); console.log(`Indexed ${Math.min(i + BATCH_SIZE, records.length)}/${records.length}`); } } // Partial update — only send changed fields export async function updateFields( indexName: string, objectID: string, fields: Record<string, any> ) { const client = getClient(); return client.partialUpdateObject({ indexName, objectID, attributesToUpdate: fields, }); }
Pattern 5: Multi-Tenant Client Factory
// src/algolia/multi-tenant.ts import { algoliasearch, type Algoliasearch } from 'algoliasearch'; const tenantClients = new Map<string, Algoliasearch>(); export function getClientForTenant(tenantId: string): Algoliasearch { if (!tenantClients.has(tenantId)) { // Each tenant might have their own Algolia app, or use index prefixes const appId = process.env[`ALGOLIA_APP_ID_${tenantId.toUpperCase()}`] || process.env.ALGOLIA_APP_ID!; const apiKey = process.env[`ALGOLIA_ADMIN_KEY_${tenantId.toUpperCase()}`] || process.env.ALGOLIA_ADMIN_KEY!; tenantClients.set(tenantId, algoliasearch(appId, apiKey)); } return tenantClients.get(tenantId)!; } // Or use a single app with index prefixing export function tenantIndex(tenantId: string, base: string): string { return `${tenantId}_${base}`; // "acme_products" }
Error Handling
| Pattern | Use Case | Benefit |
|---|---|---|
wrapper | All API calls | Prevents uncaught exceptions, structured error info |
check | Distinguishing API vs network errors | Targeted retry/recovery logic |
| After every write operation | Ensures reads see latest data |
| Batch chunking | Large datasets | Avoids record-too-big and timeout errors |
Resources
Next Steps
Apply patterns in
algolia-core-workflow-a for search implementation.