Skip to content

Searchcraft API TypeScript Client Library

The Searchcraft API TypeScript Client is a fully-featured TypeScript client that acts as a language-specific wrapper around the Searchcraft API, with support for both TypeScript and JavaScript projects.

  • Full TypeScript support with comprehensive type definitions.
  • Works with JavaScript projects as well.
  • Functional, immutable API design.
  • Composable query builder.
  • Support for all Searchcraft query modes (fuzzy, exact, dynamic) and operations.
  • Full index, federation, synonyms, and stopwords management.
  • Complete query language support.
  • Works in both Node.js and browser environments.
  • Available via NPM and CDN.
  • Zero runtime dependencies.
Terminal window
npm install @searchcraft/client
Terminal window
yarn add @searchcraft/client
Terminal window
pnpm add @searchcraft/client
<script src="https://unpkg.com/@searchcraft/client/dist/index.umd.js"></script>
<script>
const { createClient, createApiKey, createIndexName } = SearchcraftClient;
</script>
import { createClient, createApiKey, createIndexName, fuzzy } from '@searchcraft/client';
// Initialize the client
const client = createClient({
endpointUrl: 'https://your-searchcraft-instance.com',
readKey: createApiKey('your-read-key'),
});
// Perform a search
const indexName = createIndexName('your-index');
const request = fuzzy().term('search query').limit(10).buildRequest();
const response = await client.search.searchIndex(indexName, request);
console.log(`Found ${response.data.count} results`);
console.log(response.data.hits);
import { createClient, createApiKey } from '@searchcraft/client';
const client = createClient({
endpointUrl: 'https://your-instance.com',
readKey: createApiKey('your-read-key'),
ingestKey: createApiKey('your-ingest-key'), // Optional, for document operations
adminKey: createApiKey('your-admin-key'), // Optional, only needed for self-hosted clusters
timeout: 30000, // Optional, default 30 seconds
headers: {
// Optional Searchcraft headers for analytics and tracking:
'X-Sc-User-Id': 'user-123', // Unique identifier for the current user
'X-Sc-Session-Id': 'session-abc', // Session identifier for tracking user sessions
'X-Sc-User-Type': 'authenticated', // User type: 'authenticated' or 'anonymous'
},
});

Note: The adminKey is only required for self-hosted Searchcraft clusters. If you’re using Searchcraft Cloud, you do not need to provide an admin key.

import { fuzzy, exact, dynamic } from '@searchcraft/client';
// Fuzzy search (typo-tolerant, uses weight ranking, synonyms and stopwords)
const fuzzyQuery = fuzzy().term('search term').limit(20).buildRequest();
// Exact search
const exactQuery = exact().term('exact match').buildRequest();
// Dynamic search (adapts based on word count)
const dynamicQuery = dynamic().term('adaptive search').buildRequest();
const response = await client.search.searchIndex(indexName, fuzzyQuery);

The query builder provides a fluent, immutable API for constructing complex queries.

📚 Full Query Syntax Documentation: For complete details on the Searchcraft query language, operators, and advanced features, see the Query Language Reference.

import { exact } from '@searchcraft/client';
const query = exact()
.field('category', 'electronics')
.and(exact().range('price', 10, 100))
.not('discontinued')
.orderBy('rating', 'desc')
.limit(25)
.buildRequest();
const response = await client.search.searchIndex(indexName, query);
// Simple field match
exact().field('title', 'laptop').buildRequest();
// IN query
exact().fieldIn('tags', ['tech', 'gadgets', 'mobile']).buildRequest();
// Range query
exact().range('price', 10, 100).buildRequest(); // Inclusive
exact().range('price', 10, 100, false).buildRequest(); // Exclusive
// Comparison queries
exact().compare('rating', '>', 4.5).buildRequest();
exact().compare('stock', '<=', 10).buildRequest();
const from = new Date('2024-01-01');
const to = new Date('2024-12-31');
exact().range('created_at', from, to).buildRequest();
exact().compare('updated_at', '>=', new Date('2024-06-01')).buildRequest();
// AND
exact().field('active', true).and(exact().compare('price', '<', 100)).buildRequest();
// OR
exact().field('brand', 'Apple').or(exact().field('brand', 'Samsung')).buildRequest();
// NOT (exclusion)
fuzzy().term('laptop').not('refurbished').buildRequest();
// Grouping
const subQuery = exact().field('category', 'tech').or('category:science');
exact().group(subQuery).and(exact().compare('rating', '>', 4)).buildRequest();
fuzzy()
.term('query')
.limit(20)
.offset(40)
.orderBy('created_at', 'desc')
.buildRequest();

Search across multiple indices:

import { createFederationName } from '@searchcraft/client';
const federationName = createFederationName('my-federation');
const request = fuzzy().term('search term').buildRequest();
const response = await client.search.searchFederation(federationName, request);

For complex queries or when you need maximum control, you can construct request objects directly instead of using the query builder:

const request = {
query: [
{ occur: 'must', exact: { ctx: 'active:true' } },
{ occur: 'must', exact: { ctx: 'category:/electronics' } },
{ fuzzy: { ctx: 'wireless headphones' } },
],
limit: 20,
};
const response = await client.search.searchIndex(indexName, request);

Note: The query builder (e.g., fuzzy().term()...) is recommended for most use cases. Use raw objects when you need to dynamically construct complex queries or have specific requirements the builder doesn’t cover.

import { createIndexName } from '@searchcraft/client';
const indexName = createIndexName('my-index');
// Insert a document
await client.documents.insert(indexName, {
id: '123',
title: 'Product Title',
description: 'Product description',
price: 99.99,
});
// Delete a document by its id
await client.documents.delete(indexName, '123');
// Batch insert documents
await client.documents.batchInsert(indexName, [
{ id: '1', title: 'Product 1' },
{ id: '2', title: 'Product 2' },
]);
// Batch delete documents by their ids
await client.documents.batchDelete(indexName, ['1', '2']);
// Delete all documents from an index
await client.documents.deleteAll(indexName);
import { createIndexName } from '@searchcraft/client';
const indexName = createIndexName('my-index');
// List all index names
const { index_names } = await client.indices.list();
// Get index configuration
const config = await client.indices.get(indexName);
// Create a new index
await client.indices.create(indexName, {
search_fields: ['title', 'body'],
fields: {
title: { type: 'text', indexed: true, stored: true },
body: { type: 'text', indexed: true, stored: true },
},
});
// Update index configuration (partial)
await client.indices.update(indexName, {
weight_multipliers: { title: 2.0, body: 0.7 },
});
// Delete an index
await client.indices.delete(indexName);
import { createFederationName } from '@searchcraft/client';
const federationName = createFederationName('my-federation');
// List all federations
const federations = await client.federations.list();
// Get a specific federation
const federation = await client.federations.get(federationName);
// Delete a federation
await client.federations.delete(federationName);
// Get all synonyms for an index
const synonyms = await client.synonyms.get(indexName);
// Add synonyms — format: "synonym:original-term" or "many,synonyms:original,terms"
await client.synonyms.add(indexName, [
'nyc:new york city',
'usa:united states',
]);
// Delete specific synonyms by key
await client.synonyms.delete(indexName, ['nyc', 'usa']);
// Delete all synonyms
await client.synonyms.deleteAll(indexName);
// Get all stopwords for an index
const stopwords = await client.stopwords.get(indexName);
// Add custom stopwords
await client.stopwords.add(indexName, ['foo', 'bar']);
// Delete specific stopwords
await client.stopwords.delete(indexName, ['foo', 'bar']);
// Delete all custom stopwords
await client.stopwords.deleteAll(indexName);
// check() throws on error, so if it returns the service is healthy
const health = await client.health.check();
console.log('Status:', health.status); // 200
console.log('Message:', health.data); // "Searchcraft is healthy."
// Create a new Searchcraft client
createClient(config: SearchcraftConfig): SearchcraftClient
// Search an index and return typed results
searchIndex<T>(indexName: IndexName, request: SearchRequest): Promise<SearchResponse<T>>
// Search across a federation of indices and return typed results
searchFederation<T>(federationName: FederationName, request: SearchRequest): Promise<SearchResponse<T>>
// Insert a document
insert(indexName: IndexName, document: DocumentWithId): Promise<DocumentOperationResponse>
// Delete a document by id
delete(indexName: IndexName, documentId: string | number): Promise<DocumentDeleteResponse>
// Batch insert documents
batchInsert(indexName: IndexName, documents: DocumentWithId[]): Promise<DocumentOperationResponse>
// Batch delete documents by ids
batchDelete(indexName: IndexName, documentIds: (string | number)[]): Promise<DocumentDeleteResponse>
// Delete all documents from an index
deleteAll(indexName: IndexName): Promise<DocumentOperationResponse>
// Get a document by its internal Searchcraft ID (_id)
get<T>(indexName: IndexName, internalId: string): Promise<SearchHit<T>>
// List all index names
list(): Promise<IndexListResponse>
// Get the configuration for a specific index
get(indexName: IndexName): Promise<IndexConfig>
// Create a new index
create(indexName: IndexName, indexConfig: IndexConfig): Promise<IndexOperationResponse>
// Update an existing index (partial)
update(indexName: IndexName, indexConfig: Partial<IndexConfig>): Promise<IndexOperationResponse>
// Delete an index
delete(indexName: IndexName): Promise<IndexOperationResponse>
// List all federations
list(): Promise<Federation[]>
// Get a specific federation
get(federationName: FederationName): Promise<Federation>
// Delete a federation
delete(federationName: FederationName): Promise<FederationOperationResponse>
// Get all synonyms for an index
get(indexName: IndexName): Promise<SynonymsMap>
// Add synonyms in "synonym:original-term" format
add(indexName: IndexName, synonyms: string[]): Promise<SynonymOperationResponse>
// Delete specific synonyms by key
delete(indexName: IndexName, synonyms: string[]): Promise<SynonymOperationResponse>
// Delete all synonyms from an index
deleteAll(indexName: IndexName): Promise<SynonymOperationResponse>
// Get all stopwords for an index
get(indexName: IndexName): Promise<string[]>
// Add custom stopwords
add(indexName: IndexName, stopwords: string[]): Promise<StopwordOperationResponse>
// Delete specific stopwords
delete(indexName: IndexName, stopwords: string[]): Promise<StopwordOperationResponse>
// Delete all stopwords from an index
deleteAll(indexName: IndexName): Promise<StopwordOperationResponse>
// Throws on error, so if it returns the service is healthy
check(): Promise<HealthCheckResponse>
// Create a fuzzy query builder (typo-tolerant, uses weight ranking, synonyms and stopwords)
fuzzy(): QueryBuilder
// Create an exact query builder
exact(): QueryBuilder
// Create a dynamic query builder (adapts based on word count)
dynamic(): QueryBuilder

All methods return a new QueryBuilder instance (immutable):

// Add a search term
term(term: string): QueryBuilder
// Add a field:value query
field(field: string, value: string | number | boolean): QueryBuilder
// Add a field IN query
fieldIn(field: string, values: (string | number)[]): QueryBuilder
// Add a range query
range(field: string, from: string | number | Date, to: string | number | Date, inclusive?: boolean): QueryBuilder
// Add a comparison query
compare(field: string, operator: '>' | '<' | '>=' | '<=', value: number | Date): QueryBuilder
// Add an AND operator
and(query: string | QueryBuilder): QueryBuilder
// Add an OR operator
or(query: string | QueryBuilder): QueryBuilder
// Add a NOT operator
not(term: string): QueryBuilder
// Group a query with parentheses
group(query: string | QueryBuilder): QueryBuilder
// Set result limit
limit(limit: number): QueryBuilder
// Set result offset
offset(offset: number): QueryBuilder
// Set ordering
orderBy(field: string, sort?: 'asc' | 'desc'): QueryBuilder
// Set occur mode
occur(occur: 'should' | 'must'): QueryBuilder
// Build the query object
build(): SearchQuery
// Build the complete request object
buildRequest(): SearchRequest

The library uses branded types for enhanced compile-time type safety. Branded types prevent you from accidentally mixing up different string-based identifiers.

  • Compile-time safety - TypeScript catches type mismatches before runtime
  • Prevents mixing types - Can’t accidentally use an IndexName where a FederationName is expected
  • IDE support - Better autocomplete and inline error detection
  • Zero runtime overhead - Types are erased during compilation
  • Works in JavaScript - Functions work normally, just without compile-time checks
import {
createApiKey,
createIndexName,
createFederationName,
createDocumentId
} from '@searchcraft/client';
const apiKey = createApiKey('key'); // ApiKey type
const indexName = createIndexName('my-index'); // IndexName type
const federationName = createFederationName('my-fed'); // FederationName type
const docId = createDocumentId('123'); // DocumentId type
// ✅ TypeScript ensures you use the correct type
await client.search.searchIndex(indexName, request);
await client.search.searchFederation(federationName, request);
// ❌ TypeScript error - prevents mistakes at compile time
await client.search.searchFederation(indexName, request); // Error!
import {
SearchcraftError,
AuthenticationError,
NotFoundError,
ValidationError,
NetworkError,
ApiError,
} from '@searchcraft/client';
try {
const response = await client.search.searchIndex(indexName, request);
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed');
} else if (error instanceof ValidationError) {
console.error('Validation error:', error.field);
} else if (error instanceof NetworkError) {
console.error('Network error');
}
}

If you encounter any issues with the client library or wish to request additional features please open an issue at the Searchcraft Issues repository.

The Searchcraft API TypeScript client is Apache 2.0 licensed and the source code is available on Github.