fuzzyfindjs
v1.0.51
Published
A multi-language optimized fuzzy search library with phonetic matching, compound word splitting, and synonym support
Maintainers
Readme
🔍 fuzzyfindjs
🌞 Introduction
⚠️ BETA:
fuzzyFindJSis in Beta-State. Be aware of potential quality, security and performance concerns when usingfuzzyFindJS.
A multi-language fuzzy search library with phonetic matching, compound word splitting, and intelligent synonym support.
✨ Features
- 🌍 Multi-language Support: German, English, Spanish, French with auto-detection and language-specific optimizations
- 🔊 Phonetic Matching: Kölner Phonetik (German), Soundex-like algorithms for other languages
- 🧩 Compound Word Splitting: Intelligent German compound word decomposition
- 📚 Synonym Support: Built-in synonyms + custom synonym dictionaries
- 🚀 Inverted Index: Auto-enabled for large datasets (10k+ words) - 10-100x faster for 1M+ words
- 🎨 Match Highlighting: Show WHERE matches occurred with position tracking
- 🔄 Batch Search: Search multiple queries at once with auto-deduplication
- 🌍 Accent Normalization: Automatic handling of accented characters (café ↔ cafe)
- ⚖️ Field Weighting: Multi-field search with weighted scoring (title > description)
- 🚫 Stop Words Filtering: Remove common words (the, a, an) for better search quality
- 📍 Word Boundaries: Precise matching with wildcard support (cat* matches category)
- 💬 Phrase Search: Multi-word queries with quotes ("new york" finds "New York City")
- 🎯 Typo Tolerant: Handles missing letters, extra letters, transpositions, keyboard neighbors
- 🔤 N-gram Matching: Fast partial substring matching
- 📊 BM25 Scoring: Industry-standard relevance ranking for better search results
- 🎯 Bloom Filters: 50-70% faster negative lookups for large datasets
- 🔍 FQL (Fuzzy Query Language): Boolean operators (AND, OR, NOT) for complex queries
- 🔤 Phrase Parsing: Parse complex queries with quoted phrases ("new york")
- 🌍 Language Detection: Auto-detect languages from text with confidence scores
- 🔄 Incremental Updates: Add/remove items 10-100x faster than rebuilding
- 📊 Configurable Scoring: Customizable thresholds and edit distances
- 📦 Zero Dependencies: Lightweight and self-contained
📦 Installation
NPM / Yarn / PNPM
npm install fuzzyfindjsyarn add fuzzyfindjspnpm add fuzzyfindjsCDN (Browser)
For quick prototyping or simple projects, you can use FuzzyFindJS directly from a CDN:
<!-- unpkg - latest version -->
<script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
<!-- unpkg - specific version (recommended for production) -->
<script src="https://unpkg.com/[email protected]/dist/umd/fuzzyfindjs.min.js"></script>
<!-- jsdelivr - latest version -->
<script src="https://cdn.jsdelivr.net/npm/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
<!-- jsdelivr - specific version (recommended for production) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/fuzzyfindjs.min.js"></script>Bundle Size: 23.7 KB minified, 7.5 KB gzipped
The library will be available globally as FuzzyFindJS:
<!DOCTYPE html>
<html>
<head>
<title>FuzzyFindJS CDN Example</title>
</head>
<body>
<input type="text" id="search" placeholder="Search...">
<ul id="results"></ul>
<script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
<script>
// Access the library from the global FuzzyFindJS object
const { createFuzzySearch } = FuzzyFindJS;
// Create search instance
const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];
const search = createFuzzySearch(items, {
languages: ['english'],
performance: 'fast',
maxResults: 5
});
// Handle search input
document.getElementById('search').addEventListener('input', (e) => {
const results = search.search(e.target.value);
const resultsList = document.getElementById('results');
resultsList.innerHTML = results
.map(r => `<li>${r.display} (${r.score.toFixed(2)})</li>`)
.join('');
});
</script>
</body>
</html>🎮 Try the Interactive Demo
Want to test the library before installing? Run the interactive demo dashboard:
git clone https://github.com/LucaIsMyName/fuzzyfindjs.git
cd fuzzyfindjs
npm install
npm run devThe demo opens at http://localhost:3000 with:
- 🔍 Real-time fuzzy search testing
- 📚 5 pre-loaded dictionaries (German Healthcare, Cities, English Tech, Multi-language, Large Dataset)
- ⚙️ Live configuration controls (performance modes, features, thresholds)
- 📊 Performance metrics and debug information
- 💾 Auto-saves your settings to localStorage
apps/speed-test.html: Comparison between different performance modes and with fuse.jsapps/ajax-search.html: AJAX search example with a Shop-Like Interfaceapps/search-text.html: Search a large text corpus adn highlight matching Words. FQL is enabled.
🚀 Quick Start
import { createFuzzySearch } from 'fuzzyfindjs';
// Create a search instance with your dictionary
const search = createFuzzySearch([
'Hospital',
'Pharmacy',
'Doctor',
'Dentist',
'Kindergarten',
// ...
]);
// Search with typos, partial words, phonetic similarity
const results = search.search('hospitl');
// [{ display: 'Hospital', score: 0.92, ... }]
const results2 = search.search('farmacy');
// [{ display: 'Pharmacy', score: 0.88, ... }]
// German example
const germanSearch = createFuzzySearch([
'Krankenhaus',
'Apotheke',
'Zahnarzt'
], {
languages: ['german']
});
const results3 = germanSearch.search('krankenh');
// [{ display: 'Krankenhaus', score: 0.95, ... }]🎛️ API
Core Functions
buildFuzzyIndex(words, options?)
Creates a searchable index from strings or objects. Supports multi-field indexing for complex data structures.
Signature:
function buildFuzzyIndex(
words: (string | object)[],
options?: BuildIndexOptions
): FuzzyIndexParameters:
words- Array of strings or objects to indexoptions.config- Fuzzy search configuration (languages, features, performance)options.fields- Field names to index (required for objects)options.fieldWeights- Weight multipliers for each fieldoptions.languageProcessors- Custom language processorsoptions.onProgress- Progress callback(processed, total) => voidoptions.useInvertedIndex- Force inverted index (auto-enabled for 10k+ items)
Returns: FuzzyIndex object
Example 1: Simple string array
import { buildFuzzyIndex } from 'fuzzyfindjs';
const cities = ['Berlin', 'München', 'Hamburg', 'Frankfurt'];
const index = buildFuzzyIndex(cities, {
config: {
languages: ['german'],
performance: 'balanced'
}
});Example 2: Multi-field objects
const products = [
{ name: 'iPhone 15', brand: 'Apple', price: 999, category: 'Phones' },
{ name: 'Galaxy S24', brand: 'Samsung', price: 899, category: 'Phones' },
{ name: 'MacBook Pro', brand: 'Apple', price: 1999, category: 'Laptops' }
];
const index = buildFuzzyIndex(products, {
fields: ['name', 'brand', 'category', 'price'],
fieldWeights: {
name: 2.0, // Name matches weighted 2x
brand: 1.5, // Brand matches weighted 1.5x
category: 1.0 // Category normal weight
}
});Example 3: Large dataset with progress
const largeDataset = [...]; // 100k items
const index = buildFuzzyIndex(largeDataset, {
config: { performance: 'fast' },
onProgress: (processed, total) => {
console.log(`Progress: ${(processed/total*100).toFixed(1)}%`);
}
});getSuggestions(index, query, maxResults?, options?)
Searches the index and returns ranked results. Supports filtering, sorting, and advanced search options.
Signature:
function getSuggestions(
index: FuzzyIndex,
query: string,
maxResults?: number,
options?: SearchOptions
): SuggestionResult[]Parameters:
index- The fuzzy index frombuildFuzzyIndex()query- Search query stringmaxResults- Maximum results to return (default: from config)options.fuzzyThreshold- Override threshold (0-1, default: 0.75)options.languages- Filter by specific languagesoptions.matchTypes- Filter by match types ('exact', 'fuzzy', 'phonetic', etc.)options.debug- Include debug informationoptions.includeHighlights- Include match position highlightsoptions.enableFQL- Enable Fuzzy Query Language (AND/OR/NOT operators)options.filters- E-commerce filters (range, term, boolean)options.sort- Custom sorting configuration
Returns: Array of SuggestionResult objects
Example 1: Basic search
import { getSuggestions } from 'fuzzyfindjs';
const results = getSuggestions(index, 'berln', 5);
// Returns: [{ display: 'Berlin', score: 0.92, ... }]
results.forEach(r => {
console.log(`${r.display} (${(r.score * 100).toFixed(0)}% match)`);
});Example 2: Search with filters and sorting
const results = getSuggestions(index, 'phone', 10, {
filters: {
ranges: [{ field: 'price', min: 500, max: 1500 }],
terms: [{ field: 'brand', values: ['Apple', 'Samsung'] }],
booleans: [{ field: 'inStock', value: true }]
},
sort: {
primary: { field: 'price', order: 'asc' },
secondary: { field: 'rating', order: 'desc' }
}
});Example 3: Debug mode
const results = getSuggestions(index, 'hospitl', 5, {
debug: true,
includeHighlights: true
});
results.forEach(r => {
console.log(r.display);
console.log('Match type:', r._debug_matchType);
console.log('Highlights:', r.highlights);
});batchSearch(index, queries, maxResults?)
Searches multiple queries at once with automatic deduplication.
Signature:
function batchSearch(
index: FuzzyIndex,
queries: string[],
maxResults?: number
): Map<string, SuggestionResult[]>Parameters:
index- The fuzzy indexqueries- Array of search queriesmaxResults- Maximum results per query
Returns: Map of query → results
Example:
import { batchSearch } from 'fuzzyfindjs';
const queries = ['berln', 'munchen', 'hambur'];
const results = batchSearch(index, queries, 3);
results.forEach((suggestions, query) => {
console.log(`Query: "${query}"`);
suggestions.forEach(s => console.log(` - ${s.display}`));
});updateIndex(index, newWords)
Incrementally adds new items to an existing index.
Signature:
function updateIndex(
index: FuzzyIndex,
newWords: (string | object)[]
): FuzzyIndexParameters:
index- Existing index to updatenewWords- New items to add
Returns: Updated index (mutates original)
Example:
import { updateIndex } from 'fuzzyfindjs';
// Initial index
const index = buildFuzzyIndex(['Apple', 'Banana']);
// Add new items later
updateIndex(index, ['Cherry', 'Date', 'Elderberry']);
// Index now contains all 5 items
const results = getSuggestions(index, 'cherry');removeFromIndex(index, wordsToRemove)
Removes items from an existing index.
Signature:
function removeFromIndex(
index: FuzzyIndex,
wordsToRemove: string[]
): FuzzyIndexParameters:
index- Existing indexwordsToRemove- Items to remove (exact match)
Returns: Updated index (mutates original)
Example:
import { removeFromIndex } from 'fuzzyfindjs';
const index = buildFuzzyIndex(['Apple', 'Banana', 'Cherry']);
// Remove items
removeFromIndex(index, ['Banana']);
// Index now only contains Apple and Cherry
const results = getSuggestions(index, 'ban'); // Returns emptycreateFuzzySearch(dictionary, options?)
Wrapper that combines index building and searching into a single object.
Signature:
function createFuzzySearch(
dictionary: string[],
options?: {
languages?: string[];
performance?: 'fast' | 'balanced' | 'comprehensive';
maxResults?: number;
}
): { search: (query: string, maxResults?: number) => SuggestionResult[]; index: FuzzyIndex }Parameters:
dictionary- Array of strings to searchoptions- Quick configuration
Returns: Object with search() method and index property
Example:
import { createFuzzySearch } from 'fuzzyfindjs';
const fuzzy = createFuzzySearch(['Berlin', 'München', 'Hamburg'], {
languages: ['german'],
performance: 'fast',
maxResults: 5
});
// Search directly
const results = fuzzy.search('berln');
// Access underlying index
console.log(fuzzy.index.base.length); // 3Configuration
FuzzyConfig
Complete configuration interface:
interface FuzzyConfig {
// Languages to enable
languages: string[]; // default: ['english']
// Features to enable
features: FuzzyFeature[];
// Performance mode
performance: 'fast' | 'balanced' | 'comprehensive'; // default: 'balanced'
// Maximum results to return
maxResults: number; // default: 10
// Minimum query length
minQueryLength: number; // default: 2
// Fuzzy matching threshold (0-1)
fuzzyThreshold: number; // default: 0.75
// Maximum edit distance
maxEditDistance: number; // default: 2
// N-gram size for partial matching
ngramSize: number; // default: 3
// Custom synonym dictionaries
customSynonyms?: Record<string, string[]>;
// Custom normalization function
customNormalizer?: (word: string) => string;
// Enable FQL
enableFQL?: boolean;
// Enable Inverted Index
enableInvertedIndex?: boolean;
// Enable Highlighting
enableHighlighting?: boolean;
// Enable Debugging
debug?: boolean;
// Enable Progress Callback
onProgress?: (processed: number, total: number) => void;
}Available Features
type FuzzyFeature =
| 'phonetic' // Phonetic matching (sounds-like)
| 'compound' // Compound word splitting (German)
| 'synonyms' // Synonym matching
| 'keyboard-neighbors' // Keyboard typo tolerance
| 'partial-words' // Prefix/substring matching
| 'missing-letters' // Handle omitted characters
| 'extra-letters' // Handle extra characters
| 'transpositions'; // Handle swapped charactersPerformance Presets
import { PERFORMANCE_CONFIGS } from 'fuzzyfindjs';
// Fast: Minimal features, quick searches
PERFORMANCE_CONFIGS.fast
// Balanced: Good mix of features and speed (default)
PERFORMANCE_CONFIGS.balanced
// Comprehensive: All features, best accuracy
PERFORMANCE_CONFIGS.comprehensiveTypes
SuggestionResult
interface SuggestionResult {
display: string; // Formatted display text
baseWord: string; // Original matched word
isSynonym: boolean; // True if matched via synonym
score: number; // Confidence score (0-1)
language?: string; // Language that matched
}BuildIndexOptions
interface BuildIndexOptions {
config?: Partial<FuzzyConfig>;
languageProcessors?: LanguageProcessor[];
onProgress?: (processed: number, total: number) => void;
}SearchOptions
interface SearchOptions {
maxResults?: number;
fuzzyThreshold?: number;
languages?: string[];
matchTypes?: MatchType[];
debug?: boolean;
}Utility Functions
serializeIndex(index)
Converts index to JSON string for storage.
import { serializeIndex } from 'fuzzyfindjs';
const index = buildFuzzyIndex(['Apple', 'Banana']);
const json = serializeIndex(index);
localStorage.setItem('search-index', json);deserializeIndex(json)
Reconstructs index from JSON string.
import { deserializeIndex } from 'fuzzyfindjs';
const json = localStorage.getItem('search-index');
const index = deserializeIndex(json);
const results = getSuggestions(index, 'aple');saveIndexToLocalStorage(index, key)
Saves index to browser localStorage.
import { saveIndexToLocalStorage } from 'fuzzyfindjs';
saveIndexToLocalStorage(index, 'my-search-index');loadIndexFromLocalStorage(key)
Loads index from browser localStorage.
import { loadIndexFromLocalStorage } from 'fuzzyfindjs';
const index = loadIndexFromLocalStorage('my-search-index');
if (index) {
const results = getSuggestions(index, 'query');
}getSerializedSize(index)
Returns size of serialized index in bytes.
import { getSerializedSize } from 'fuzzyfindjs';
const sizeBytes = getSerializedSize(index);
console.log(`Index size: ${(sizeBytes / 1024).toFixed(2)} KB`);applyFilters(results, filters)
Applies filters to search results.
import { applyFilters } from 'fuzzyfindjs';
const allResults = getSuggestions(index, 'phone');
const filtered = applyFilters(allResults, {
ranges: [{ field: 'price', min: 500, max: 1000 }],
terms: [{ field: 'brand', values: ['Apple', 'Samsung'] }],
booleans: [{ field: 'inStock', value: true }]
});applySorting(results, sortConfig)
Applies custom sorting to results.
import { applySorting } from 'fuzzyfindjs';
const results = getSuggestions(index, 'laptop');
const sorted = applySorting(results, {
primary: { field: 'price', order: 'asc' },
secondary: { field: 'rating', order: 'desc' },
keepRelevance: true
});calculateHighlights(match, query, text)
Calculates character positions where query matches text.
import { calculateHighlights } from 'fuzzyfindjs';
const highlights = calculateHighlights(match, 'berln', 'Berlin');
// Returns: [{ start: 0, end: 3 }, { start: 4, end: 5 }]formatHighlightedHTML(text, highlights)
Wraps highlighted portions in HTML tags.
import { formatHighlightedHTML } from 'fuzzyfindjs';
const html = formatHighlightedHTML('Berlin', highlights);
// Returns: '<mark>Ber</mark>li<mark>n</mark>'removeAccents(text)
Removes accents from text.
import { removeAccents } from 'fuzzyfindjs';
removeAccents('café'); // 'cafe'
removeAccents('naïve'); // 'naive'
removeAccents('Müller'); // 'Muller'hasAccents(text)
Checks if text contains accents.
import { hasAccents } from 'fuzzyfindjs';
hasAccents('café'); // true
hasAccents('cafe'); // falsenormalizeForComparison(text)
Normalizes text for comparison (lowercase + remove accents).
import { normalizeForComparison } from 'fuzzyfindjs';
normalizeForComparison('Café'); // 'cafe'
normalizeForComparison('NAÏVE'); // 'naive'getAccentVariants(text)
Generates all accent variants of text.
import { getAccentVariants } from 'fuzzyfindjs';
getAccentVariants('cafe');
// Returns: ['cafe', 'café', 'cafè', 'cafê', ...]filterStopWords(text, stopWords)
Removes stop words from text.
import { filterStopWords } from 'fuzzyfindjs';
filterStopWords('the quick brown fox', ['the', 'a']);
// Returns: 'quick brown fox'isStopWord(word, stopWords)
Checks if word is a stop word.
import { isStopWord } from 'fuzzyfindjs';
isStopWord('the', ['the', 'a', 'an']); // true
isStopWord('fox', ['the', 'a', 'an']); // falsegetStopWordsForLanguages(languages)
Gets stop words for specified languages.
import { getStopWordsForLanguages } from 'fuzzyfindjs';
const stopWords = getStopWordsForLanguages(['english', 'german']);
// Returns: ['the', 'a', 'an', 'der', 'die', 'das', ...]DEFAULT_STOP_WORDS
Pre-defined stop words for common languages.
import { DEFAULT_STOP_WORDS } from 'fuzzyfindjs';
console.log(DEFAULT_STOP_WORDS.english);
// ['the', 'a', 'an', 'is', 'at', 'on', ...]isWordBoundary(char)
Checks if character is a word boundary.
import { isWordBoundary } from 'fuzzyfindjs';
isWordBoundary(' '); // true
isWordBoundary('-'); // true
isWordBoundary('a'); // falsematchesAtWordBoundary(text, query)
Checks if query matches at word boundary.
import { matchesAtWordBoundary } from 'fuzzyfindjs';
matchesAtWordBoundary('hello world', 'world'); // true
matchesAtWordBoundary('hello world', 'orld'); // falsefindWordBoundaryMatches(text, query)
Finds all word boundary matches.
import { findWordBoundaryMatches } from 'fuzzyfindjs';
findWordBoundaryMatches('hello world hello', 'hello');
// Returns: [{ start: 0, end: 5 }, { start: 12, end: 17 }]matchesWord(text, word)
Checks if text contains exact word.
import { matchesWord } from 'fuzzyfindjs';
matchesWord('hello world', 'hello'); // true
matchesWord('hello world', 'hel'); // falsematchesWildcard(text, pattern)
Matches text against wildcard pattern.
import { matchesWildcard } from 'fuzzyfindjs';
matchesWildcard('application', 'app*'); // true
matchesWildcard('category', 'cat*'); // true
matchesWildcard('dog', 'cat*'); // falsedataToIndex(data, options)
Extracts searchable text from structured data.
import { dataToIndex } from 'fuzzyfindjs';
const html = '<div><h1>Title</h1><p>Content</p></div>';
const words = dataToIndex(html, { type: 'html' });
// Returns: ['Title', 'Content']
const json = { name: 'John', email: '[email protected]' };
const words2 = dataToIndex(json, { type: 'json' });
// Returns: ['John', '[email protected]']dataToIndexAsync(data, options)
Async version of dataToIndex for large datasets.
import { dataToIndexAsync } from 'fuzzyfindjs';
const largeHtml = '...'; // Large HTML document
const words = await dataToIndexAsync(largeHtml, {
type: 'html',
chunkSize: 1000
});parseQuery(query)
Parses query into terms and phrases.
import { parseQuery } from 'fuzzyfindjs';
const parsed = parseQuery('hello "new york" world');
// Returns: {
// terms: ['hello', 'world'],
// phrases: ['new york'],
// hasPhrases: true
// }hasPhraseSyntax(query)
Checks if query contains phrase syntax (quotes).
import { hasPhraseSyntax } from 'fuzzyfindjs';
hasPhraseSyntax('"new york"'); // true
hasPhraseSyntax('new york'); // falsenormalizePhrase(phrase)
Normalizes phrase for matching.
import { normalizePhrase } from 'fuzzyfindjs';
normalizePhrase('"New York"'); // 'new york'splitPhraseWords(phrase)
Splits phrase into individual words.
import { splitPhraseWords } from 'fuzzyfindjs';
splitPhraseWords('new york city'); // ['new', 'york', 'city']detectLanguages(text)
Detects languages in text.
import { detectLanguages } from 'fuzzyfindjs';
const languages = detectLanguages('Hello Krankenhaus café');
// Returns: ['english', 'german', 'french']detectLanguagesWithConfidence(text)
Detects languages with confidence scores.
import { detectLanguagesWithConfidence } from 'fuzzyfindjs';
const results = detectLanguagesWithConfidence('Hello world');
// Returns: [{ language: 'english', confidence: 0.95 }]sampleTextForDetection(words, sampleSize)
Samples text for language detection.
import { sampleTextForDetection } from 'fuzzyfindjs';
const sample = sampleTextForDetection(largeArray, 100);
// Returns first 100 items concatenatedisValidLanguage(language)
Checks if language is supported.
import { isValidLanguage } from 'fuzzyfindjs';
isValidLanguage('english'); // true
isValidLanguage('klingon'); // falsenormalizeLanguageCode(code)
Normalizes language code.
import { normalizeLanguageCode } from 'fuzzyfindjs';
normalizeLanguageCode('en'); // 'english'
normalizeLanguageCode('de'); // 'german'SearchCache
LRU cache for search results.
import { SearchCache } from 'fuzzyfindjs';
const cache = new SearchCache(100); // Max 100 entries
// Manual caching
cache.set('query', results, 10);
const cached = cache.get('query', 10);
// Stats
const stats = cache.getStats();
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
// Clear
cache.clear();LRUCache
Generic LRU cache.
import { LRUCache } from 'fuzzyfindjs';
const cache = new LRUCache<string, any>(50);
cache.set('key', { data: 'value' });
const value = cache.get('key');
console.log(cache.size); // 1
cache.clear();DEFAULT_CONFIG
Default configuration object.
import { DEFAULT_CONFIG } from 'fuzzyfindjs';
console.log(DEFAULT_CONFIG.languages); // ['english']
console.log(DEFAULT_CONFIG.performance); // 'balanced'
console.log(DEFAULT_CONFIG.fuzzyThreshold); // 0.75PERFORMANCE_CONFIGS
Pre-defined performance configurations.
import { PERFORMANCE_CONFIGS } from 'fuzzyfindjs';
// Fast mode
const fastConfig = PERFORMANCE_CONFIGS.fast;
// Balanced mode
const balancedConfig = PERFORMANCE_CONFIGS.balanced;
// Comprehensive mode
const comprehensiveConfig = PERFORMANCE_CONFIGS.comprehensive;mergeConfig(userConfig)
Merges user config with defaults.
import { mergeConfig } from 'fuzzyfindjs';
const config = mergeConfig({
languages: ['german'],
maxResults: 20
});
// Returns complete config with defaults filled in💡 Usage Examples
Browser: CDN Usage
Basic Search with Vanilla JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FuzzyFindJS - Simple Search</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; }
input { width: 100%; padding: 10px; font-size: 16px; }
ul { list-style: none; padding: 0; }
li { padding: 8px; border-bottom: 1px solid #eee; }
.score { color: #666; font-size: 0.9em; }
</style>
</head>
<body>
<h1>🔍 Fuzzy Search Demo</h1>
<input type="text" id="search" placeholder="Type to search..." autofocus>
<ul id="results"></ul>
<script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
<script>
const { createFuzzySearch } = FuzzyFindJS;
const fruits = [
'Apple', 'Apricot', 'Banana', 'Blackberry', 'Blueberry',
'Cherry', 'Coconut', 'Cranberry', 'Date', 'Dragonfruit',
'Elderberry', 'Fig', 'Grape', 'Grapefruit', 'Guava',
'Kiwi', 'Lemon', 'Lime', 'Mango', 'Orange', 'Papaya',
'Peach', 'Pear', 'Pineapple', 'Plum', 'Raspberry',
'Strawberry', 'Watermelon'
];
const search = createFuzzySearch(fruits, {
languages: ['english'],
performance: 'fast',
maxResults: 10
});
const searchInput = document.getElementById('search');
const resultsList = document.getElementById('results');
searchInput.addEventListener('input', (e) => {
const query = e.target.value;
if (query.length < 2) {
resultsList.innerHTML = '';
return;
}
const results = search.search(query);
if (results.length === 0) {
resultsList.innerHTML = '<li>No results found</li>';
return;
}
resultsList.innerHTML = results
.map(r => `
<li>
<strong>${r.display}</strong>
<span class="score">Score: ${r.score.toFixed(2)}</span>
</li>
`)
.join('');
});
</script>
</body>
</html>Multi-language Search
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Multi-language Fuzzy Search</title>
</head>
<body>
<h1>🌍 Multi-language Search</h1>
<input type="text" id="search" placeholder="Search in any language...">
<div id="results"></div>
<script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
<script>
const { createFuzzySearch } = FuzzyFindJS;
// Dictionary with German, English, French, Spanish words
const places = [
'Krankenhaus', 'Hospital', 'Hôpital', 'Hospital',
'Schule', 'School', 'École', 'Escuela',
'Apotheke', 'Pharmacy', 'Pharmacie', 'Farmacia',
'Bahnhof', 'Station', 'Gare', 'Estación',
'Flughafen', 'Airport', 'Aéroport', 'Aeropuerto'
];
const search = createFuzzySearch(places, {
languages: ['german', 'english', 'french', 'spanish'],
performance: 'comprehensive',
features: ['phonetic', 'partial-words', 'synonyms'],
maxResults: 15
});
document.getElementById('search').addEventListener('input', (e) => {
const results = search.search(e.target.value);
const output = document.getElementById('results');
output.innerHTML = results.length
? results.map(r => `<p>${r.display} (${r.score.toFixed(2)})</p>`).join('')
: '<p>No results</p>';
});
</script>
</body>
</html>Autocomplete Dropdown
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Autocomplete with FuzzyFindJS</title>
<style>
.autocomplete {
position: relative;
width: 400px;
margin: 50px auto;
}
.autocomplete input {
width: 100%;
padding: 12px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 4px;
}
.autocomplete-items {
position: absolute;
border: 1px solid #ddd;
border-top: none;
z-index: 99;
top: 100%;
left: 0;
right: 0;
max-height: 300px;
overflow-y: auto;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.autocomplete-item {
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.autocomplete-item:hover {
background-color: #e9e9e9;
}
.autocomplete-active {
background-color: #4CAF50 !important;
color: white;
}
</style>
</head>
<body>
<div class="autocomplete">
<input type="text" id="cityInput" placeholder="Search cities...">
<div id="autocomplete-list" class="autocomplete-items"></div>
</div>
<script src="https://unpkg.com/fuzzyfindjs@latest/dist/umd/fuzzyfindjs.min.js"></script>
<script>
const { createFuzzySearch } = FuzzyFindJS;
const cities = [
'Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt',
'Stuttgart', 'Düsseldorf', 'Dortmund', 'Essen', 'Leipzig',
'Bremen', 'Dresden', 'Hannover', 'Nürnberg', 'Duisburg'
];
const search = createFuzzySearch(cities, {
languages: ['german'],
performance: 'fast',
maxResults: 8
});
const input = document.getElementById('cityInput');
const list = document.getElementById('autocomplete-list');
input.addEventListener('input', (e) => {
const query = e.target.value;
list.innerHTML = '';
if (query.length < 2) return;
const results = search.search(query);
results.forEach(result => {
const item = document.createElement('div');
item.className = 'autocomplete-item';
item.textContent = result.display;
item.addEventListener('click', () => {
input.value = result.display;
list.innerHTML = '';
});
list.appendChild(item);
});
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.autocomplete')) {
list.innerHTML = '';
}
});
</script>
</body>
</html>Frontend: Autocomplete Search
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
// Build index once on app initialization
const cities = ['Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt'];
const cityIndex = buildFuzzyIndex(cities, {
config: { languages: ['german'], performance: 'fast' }
});
// Use in search input handler
function handleSearchInput(query: string) {
const results = getSuggestions(cityIndex, query, 5);
updateAutocompleteDropdown(results);
}
// Handles: "munchen" → "München", "berln" → "Berlin"Backend: API Search Endpoint
import express from 'express';
import { createFuzzySearch } from 'fuzzyfindjs';
const app = express();
// Load product catalog
const products = await loadProductsFromDatabase();
const productNames = products.map(p => p.name);
// Build search index
const search = createFuzzySearch(productNames, {
languages: ['english', 'german'],
performance: 'balanced',
maxResults: 20
});
// Search endpoint
app.get('/api/search', (req, res) => {
const query = req.query.q as string;
const results = search.search(query);
// Map back to full product objects
const products = results.map(r =>
products.find(p => p.name === r.baseWord)
);
res.json(products);
});Async Dictionary Loading (Fetch from API/Database)
The library is fully synchronous - no async methods needed! This gives you complete control over when and how to load your dictionary.
Pattern 1: Load Once on App Startup
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
// Fetch dictionary from API
async function initializeSearch() {
// Load your dictionary from any source
const response = await fetch('/api/dictionary');
const words = await response.json();
// Build index synchronously (fast!)
const index = buildFuzzyIndex(words, {
config: {
languages: ['english'],
performance: 'balanced'
}
});
return index;
}
// Initialize once
let searchIndex;
initializeSearch().then(index => {
searchIndex = index;
console.log('Search ready!');
});
// Use in your app
function search(query) {
if (!searchIndex) {
return []; // Or show loading state
}
return getSuggestions(searchIndex, query);
}Pattern 2: React with useState/useEffect
import { useState, useEffect } from 'react';
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
function SearchComponent() {
const [index, setIndex] = useState(null);
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(true);
// Load dictionary on mount
useEffect(() => {
async function loadDictionary() {
try {
const response = await fetch('/api/products');
const products = await response.json();
const words = products.map(p => p.name);
// Build index (synchronous, fast)
const searchIndex = buildFuzzyIndex(words, {
config: { languages: ['english'], performance: 'fast' }
});
setIndex(searchIndex);
} catch (error) {
console.error('Failed to load dictionary:', error);
} finally {
setLoading(false);
}
}
loadDictionary();
}, []);
const handleSearch = (query) => {
if (!index || !query) {
setResults([]);
return;
}
const matches = getSuggestions(index, query, 10);
setResults(matches);
};
if (loading) return <div>Loading search...</div>;
return (
<div>
<input
type="text"
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search..."
/>
<ul>
{results.map(r => <li key={r.baseWord}>{r.display}</li>)}
</ul>
</div>
);
}Pattern 3: Node.js with Database
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
import { MongoClient } from 'mongodb';
class ProductSearch {
private index = null;
async initialize() {
// Connect to database
const client = await MongoClient.connect(process.env.MONGO_URL);
const db = client.db('myapp');
// Fetch all product names
const products = await db.collection('products')
.find({}, { projection: { name: 1 } })
.toArray();
const words = products.map(p => p.name);
// Build index synchronously
this.index = buildFuzzyIndex(words, {
config: {
languages: ['english', 'german'],
performance: 'balanced',
maxResults: 20
},
onProgress: (processed, total) => {
console.log(`Indexing: ${processed}/${total}`);
}
});
console.log(`Indexed ${words.length} products`);
await client.close();
}
search(query) {
if (!this.index) {
throw new Error('Search not initialized');
}
return getSuggestions(this.index, query);
}
}
// Usage
const search = new ProductSearch();
await search.initialize();
app.get('/search', (req, res) => {
const results = search.search(req.query.q);
res.json(results);
});Pattern 4: Dynamic Re-indexing
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
class DynamicSearch {
private index = null;
private lastUpdate = 0;
private updateInterval = 5 * 60 * 1000; // 5 minutes
async refreshIndex() {
const now = Date.now();
// Only refresh if enough time has passed
if (now - this.lastUpdate < this.updateInterval) {
return;
}
console.log('Refreshing search index...');
// Fetch latest data
const response = await fetch('/api/dictionary?updated_since=' + this.lastUpdate);
const words = await response.json();
// Rebuild index (fast, even for large dictionaries)
this.index = buildFuzzyIndex(words, {
config: { languages: ['english'], performance: 'fast' }
});
this.lastUpdate = now;
console.log(`Index refreshed with ${words.length} words`);
}
async search(query) {
// Auto-refresh if needed
await this.refreshIndex();
if (!this.index) {
throw new Error('Index not initialized');
}
return getSuggestions(this.index, query);
}
}Key Points
✅ No async needed in the library - buildFuzzyIndex() is synchronous and fast
✅ Fetch your dictionary however you want:
- REST API (
fetch,axios) - Database (MongoDB, PostgreSQL, etc.)
- File system (
fs.readFile) - GraphQL
- WebSocket
- Any async source!
✅ Index building is fast:
- 1,000 words: ~10ms
- 10,000 words: ~50-250ms (depending on performance mode)
- 100,000 words: ~500ms-2s
✅ Build once, search many times:
- Building the index is the "expensive" operation
- Searching is extremely fast (<1ms per query)
- Cache the index in memory for best performance
✅ Re-indexing strategies:
- Static: Build once on app startup
- Periodic: Rebuild every N minutes/hours
- On-demand: Rebuild when data changes
- Incremental: Keep old index, build new one in background
Multi-language Search
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
const multiLangDictionary = [
'Hospital', 'Krankenhaus', 'Hôpital', 'Hospital',
'School', 'Schule', 'École', 'Escuela',
'Car', 'Auto', 'Voiture', 'Coche'
];
const index = buildFuzzyIndex(multiLangDictionary, {
config: {
languages: ['english', 'german', 'french', 'spanish'],
performance: 'comprehensive',
features: ['phonetic', 'partial-words', 'synonyms']
}
});
// Search works across all languages
getSuggestions(index, 'kranken'); // Finds "Krankenhaus"
getSuggestions(index, 'hospit'); // Finds all hospital variants
getSuggestions(index, 'ecol'); // Finds "École", "Escuela", "School"Custom Synonyms
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
const medicalTerms = ['Physician', 'Nurse', 'Surgeon', 'Therapist'];
const index = buildFuzzyIndex(medicalTerms, {
config: {
languages: ['english'],
features: ['synonyms', 'phonetic'],
customSynonyms: {
'physician': ['doctor', 'doc', 'md'],
'nurse': ['rn', 'caregiver'],
'surgeon': ['doctor', 'md', 'specialist']
}
}
});
// Synonym matching works
getSuggestions(index, 'doctor'); // Finds "Physician", "Surgeon"
getSuggestions(index, 'doc'); // Finds "Physician"Large Dataset with Progress
import { buildFuzzyIndex } from 'fuzzyfindjs';
const largeDictionary = await loadMillionWords();
const index = buildFuzzyIndex(largeDictionary, {
config: {
languages: ['english'],
performance: 'fast' // Optimize for large datasets
},
onProgress: (processed, total) => {
const percent = ((processed / total) * 100).toFixed(1);
console.log(`Building index: ${percent}%`);
updateProgressBar(percent);
}
});React Hook Example
import { useState, useMemo } from 'react';
import { createFuzzySearch } from 'fuzzyfindjs';
function useSearch(dictionary: string[]) {
const [query, setQuery] = useState('');
const search = useMemo(() =>
createFuzzySearch(dictionary, {
languages: ['english'],
performance: 'fast',
maxResults: 10
}),
[dictionary]
);
const results = useMemo(() =>
query.length >= 2 ? search.search(query) : [],
[query, search]
);
return { query, setQuery, results };
}
// Usage in component
function SearchComponent() {
const { query, setQuery, results } = useSearch(myDictionary);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search..."
/>
<ul>
{results.map(r => (
<li key={r.baseWord}>
{r.display} <span>({r.score.toFixed(2)})</span>
</li>
))}
</ul>
</div>
);
}Node.js CLI Tool
#!/usr/bin/env node
import { createFuzzySearch } from 'fuzzyfindjs';
import * as readline from 'readline';
const dictionary = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
const search = createFuzzySearch(dictionary);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('Fuzzy Search CLI - Type to search, Ctrl+C to exit\n');
rl.on('line', (query) => {
const results = search.search(query);
if (results.length === 0) {
console.log('No results found\n');
} else {
results.forEach((r, i) => {
console.log(`${i + 1}. ${r.display} (${r.score.toFixed(2)})`);
});
console.log();
}
});Advanced: Custom Language Processor
import {
buildFuzzyIndex,
BaseLanguageProcessor,
type FuzzyFeature
} from 'fuzzyfindjs';
class ItalianProcessor extends BaseLanguageProcessor {
readonly language = 'italian';
readonly displayName = 'Italiano';
readonly supportedFeatures: FuzzyFeature[] = [
'phonetic', 'partial-words', 'synonyms'
];
normalize(text: string): string {
return text.toLowerCase()
.replace(/à/g, 'a')
.replace(/è/g, 'e')
.replace(/é/g, 'e')
.replace(/ì/g, 'i')
.replace(/ò/g, 'o')
.replace(/ù/g, 'u');
}
getSynonyms(word: string): string[] {
const synonyms: Record<string, string[]> = {
'ciao': ['salve', 'buongiorno'],
'casa': ['abitazione', 'dimora']
};
return synonyms[this.normalize(word)] || [];
}
}
// Use custom processor
const italianWords = ['Ciao', 'Casa', 'Città'];
const index = buildFuzzyIndex(italianWords, {
languageProcessors: [new ItalianProcessor()]
});🎯 Performance Tips
1. Choose the Right Performance Mode
// For autocomplete (speed critical)
{ performance: 'fast' }
// For general search (balanced)
{ performance: 'balanced' }
// For critical searches (accuracy critical)
{ performance: 'comprehensive' }2. Limit Features for Large Datasets
// Faster indexing and searching
{
features: ['partial-words', 'missing-letters'],
maxEditDistance: 1
}3. Build Index Once, Search Many Times
// ❌ Bad: Rebuilding index on every search
function search(query) {
const index = buildFuzzyIndex(dictionary);
return getSuggestions(index, query);
}
// ✅ Good: Build once, reuse
const index = buildFuzzyIndex(dictionary);
function search(query) {
return getSuggestions(index, query);
}4. Use Appropriate Thresholds
// Strict matching (fewer, more accurate results)
{ fuzzyThreshold: 0.9 }
// Lenient matching (more results, some false positives)
{ fuzzyThreshold: 0.6 }5. Inverted Index for Large Datasets
The library automatically uses an inverted index for datasets with 10,000+ words, providing 10-100x performance improvement:
// Automatically uses inverted index for large datasets
const largeDictionary = Array.from({ length: 100000 }, (_, i) => `Word${i}`);
const index = buildFuzzyIndex(largeDictionary);
// ✨ Inverted index automatically enabled!
// Force inverted index for smaller datasets (optional)
const index = buildFuzzyIndex(smallDictionary, {
useInvertedIndex: true // Manual override
});
// Or via config
const index = buildFuzzyIndex(dictionary, {
config: {
useInvertedIndex: true
}
});Performance comparison:
| Dataset Size | Classic Index | Inverted Index | Speedup | | --------------- | ------------- | -------------- | ------- | | 1,000 words | 1ms | 1ms | 1x | | 10,000 words | 5ms | 2ms | 2.5x | | 100,000 words | 50ms | 5ms | 10x | | 1,000,000 words | 500ms | 10ms | 50x |
No code changes required - the library automatically detects and uses the optimal index structure!
6. Match Highlighting
Get exact positions of matched characters for UI highlighting:
// Enable highlighting
const results = getSuggestions(index, 'app', 5, {
includeHighlights: true
});
// Results include highlight positions
console.log(results[0].highlights);
// → [{ start: 0, end: 3, type: 'prefix' }]
// Format as HTML
import { formatHighlightedHTML } from 'fuzzyfindjs';
const html = formatHighlightedHTML(results[0].display, results[0].highlights);
// → '<mark class="highlight highlight--prefix">App</mark>lication'Perfect for:
- Search result highlighting
- Autocomplete UI
- Showing users WHY something matched
7. Search Result Caching
Automatic LRU cache for 10-100x faster repeated queries:
// Cache is enabled by default!
const index = buildFuzzyIndex(dictionary);
// First search - cache miss
getSuggestions(index, 'app', 5); // ~5ms
// Second search - cache hit!
getSuggestions(index, 'app', 5); // ~0.1ms (50x faster!)
// Check cache stats
const stats = index._cache.getStats();
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
// Disable cache if needed
const index = buildFuzzyIndex(dictionary, {
config: { enableCache: false }
});
// Custom cache size
const index = buildFuzzyIndex(dictionary, {
config: { cacheSize: 200 } // Default: 100
});Ideal for:
- Autocomplete (users type incrementally)
- Search-as-you-type
- Repeated queries
8. Index Serialization
Save and load indices for 100x faster startup:
import { serializeIndex, deserializeIndex } from 'fuzzyfindjs';
// Build index once
const index = buildFuzzyIndex(largeDictionary);
// Serialize to JSON
const serialized = serializeIndex(index);
localStorage.setItem('search-index', serialized);
// Later: Load instantly (100x faster!)
const loaded = await deserializeIndex(localStorage.getItem('search-index'));
const results = getSuggestions(loaded, 'query'); // Works immediately!
// Get serialized size
import { getSerializedSize } from 'fuzzyfindjs';
const sizeInBytes = getSerializedSize(index);
console.log(`Index size: ${(sizeInBytes / 1024).toFixed(2)} KB`);Perfect for:
- Server-side apps (save to disk)
- Browser apps (save to localStorage)
- Skipping index rebuild on startup
Performance:
- Index building: ~250ms (10k words)
- Serialization: ~10ms
- Deserialization: ~5ms
- 50-100x faster than rebuilding!
9. Batch Search API
Search multiple queries at once with automatic deduplication:
import { batchSearch } from 'fuzzyfindjs';
// Search multiple queries efficiently
const results = batchSearch(index, ['apple', 'banana', 'cherry']);
// → { apple: [...], banana: [...], cherry: [...] }
// Automatic deduplication
const results = batchSearch(index, ['app', 'app', 'ban', 'app']);
// → { app: [...], ban: [...] } // Only 2 queries executed!
// Supports all search options
const results = batchSearch(index, ['app', 'ban'], 5, {
includeHighlights: true,
fuzzyThreshold: 0.8
});
// Perfect for multi-field forms
const formResults = batchSearch(index, [
formData.name,
formData.email,
formData.phone
]);Benefits:
- Deduplicates identical queries automatically
- Cache-friendly - repeated queries hit cache
- Cleaner code - one call instead of multiple
- Type-safe - full TypeScript support
Use cases:
- Multi-field search forms
- Batch processing
- Server-side batch queries
- Deduplicating search requests
10. Accent Normalization
Automatic handling of accented characters for international support:
import { buildFuzzyIndex, getSuggestions, removeAccents } from 'fuzzyfindjs';
// Utility function
removeAccents('café'); // → 'cafe'
removeAccents('José'); // → 'Jose'
removeAccents('Müller'); // → 'Muller'
removeAccents('naïve'); // → 'naive'
// Automatic in search - works bidirectionally!
const index = buildFuzzyIndex(['café', 'naïve', 'José', 'Müller']);
// Search without accents - finds accented words
getSuggestions(index, 'cafe'); // ✅ Finds 'café'
getSuggestions(index, 'naive'); // ✅ Finds 'naïve'
getSuggestions(index, 'Jose'); // ✅ Finds 'José'
getSuggestions(index, 'Muller'); // ✅ Finds 'Müller'
// Search with accents - also works!
const index2 = buildFuzzyIndex(['cafe', 'naive']);
getSuggestions(index2, 'café'); // ✅ Finds 'cafe'
getSuggestions(index2, 'naïve'); // ✅ Finds 'naive'Supported Languages:
- 🇫🇷 French: café, crème, naïve, résumé, château
- 🇪🇸 Spanish: José, María, niño, señor, mañana
- 🇩🇪 German: Müller, Köln, Zürich, Straße, Äpfel
- 🇵🇹 Portuguese: São Paulo, açúcar, coração
- 🇵🇱 Polish: Łódź, Kraków, Gdańsk
- And 100+ more accented characters!
Benefits:
- Automatic - no configuration needed
- Bidirectional - café ↔ cafe both work
- Preserves original - display text keeps accents
- Zero overhead - indexed once, searched fast
Perfect for:
- International applications
- Multi-language search
- User-friendly input (users can't always type accents)
- E-commerce with international products
11. Field Weighting (Opt-In)
⚠️ This is an opt-in feature - By default, FuzzyFindJS works with simple string arrays. Field weighting is only activated when you explicitly provide the fields option.
Multi-field search with weighted scoring for better ranking:
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
// ✅ DEFAULT: Simple string array (no field weighting)
const simpleIndex = buildFuzzyIndex(['Apple iPhone', 'Samsung Phone', 'Google Pixel']);
const results1 = getSuggestions(simpleIndex, 'phone');
// Works normally with fuzzy matching
// ✅ OPT-IN: Multi-field search with objects
const products = [
{ id: 1, title: 'Apple iPhone', description: 'Smartphone with great camera', category: 'Electronics' },
{ id: 2, title: 'Apple Pie Recipe', description: 'Delicious dessert', category: 'Food' },
{ id: 3, title: 'Samsung Phone', description: 'Apple-like design', category: 'Electronics' }
];
// You MUST specify fields when indexing objects
const index = buildFuzzyIndex(products, {
fields: ['title', 'description', 'category'], // Required for objects
fieldWeights: { // Optional: customize weights
title: 2.0, // Title matches worth 2x
description: 1.0, // Description matches normal weight
category: 1.5 // Category matches worth 1.5x
}
});
// Search for "apple"
const results = getSuggestions(index, 'apple');
// Result order (by weighted score):
// 1. Apple iPhone (title match - 2x weight)
// 2. Apple Pie Recipe (title match - 2x weight)
// 3. Samsung Phone (description match - 1x weight)
// Access field data in results
console.log(results[0].fields);
// → { title: 'Apple iPhone', description: 'Smartphone...', category: 'Electronics' }Important Notes:
- 🔴 Required: When indexing objects, you must specify
fieldsoption - ✅ Optional:
fieldWeightsare optional (defaults to 1.0 for all fields) - ✅ Backwards Compatible: String arrays work exactly as before
- ⚡ No Performance Impact: Only activated when using objects with fields
Use Cases:
- 📱 E-commerce: Product name > description > tags
- 📄 Documents: Title > content > metadata
- 👤 User Search: Name > email > bio
- 🎵 Music: Song title > artist > album
- 🏢 Companies: Company name > industry > description
Features:
- ✅ Weighted Scoring - Boost important fields
- ✅ Multi-Field Search - Search across object properties
- ✅ Field Preservation - Results include all field data
- ✅ Opt-In Only - Doesn't affect simple string arrays
- ✅ Automatic Defaults - Unspecified fields default to 1.0
Example: Document Search
const docs = [
{ title: 'TypeScript Guide', content: 'Learn TypeScript basics', tags: 'programming' },
{ title: 'JavaScript Intro', content: 'TypeScript is a superset', tags: 'tutorial' }
];
const index = buildFuzzyIndex(docs, {
fields: ['title', 'content', 'tags'], // Required!
fieldWeights: { // Optional
title: 3.0, // Title most important
content: 1.0, // Content normal
tags: 2.0 // Tags important
}
});
const results = getSuggestions(index, 'typescript');
// "TypeScript Guide" ranks first (title match with 3x weight)12. E-Commerce: Filtering & Sorting
Built-in support for e-commerce use cases with filtering and custom sorting:
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
// Index products with multiple fields
const products = [
{ name: "Nike Air Max", brand: "Nike", price: 120, rating: 4.5, inStock: true },
{ name: "Adidas Ultraboost", brand: "Adidas", price: 180, rating: 4.8, inStock: true },
{ name: "Nike React", brand: "Nike", price: 90, rating: 4.2, inStock: false },
{ name: "Puma RS-X", brand: "Puma", price: 110, rating: 4.0, inStock: true },
];
const index = buildFuzzyIndex(products, {
fields: ["name", "brand", "price", "rating", "inStock"],
});
// Search with filters and sorting
const results = getSuggestions(index, "nike", 10, {
filters: {
// Range filters (numeric fields)
ranges: [
{ field: "price", min: 80, max: 150 },
{ field: "rating", min: 4.0 }
],
// Term filters (categorical fields)
terms: [
{ field: "brand", values: ["Nike", "Adidas"] }
],
// Boolean filters
booleans: [
{ field: "inStock", value: true }
]
},
sort: {
// Primary sort: price ascending
primary: { field: "price", order: "asc" },
// Secondary sort: rating descending (tie-breaker)
secondary: { field: "rating", order: "desc" },
// Keep relevance as final tie-breaker (default: true)
keepRelevance: true
}
});
// Results are filtered and sorted
results.forEach(result => {
console.log(result.display); // "Nike Air Max"
console.log(result.fields?.price); // 120
console.log(result.fields?.rating); // 4.5
console.log(result.fields?.inStock); // true
});Filter Types:
// Range Filter - Numeric comparisons
interface RangeFilter {
field: string;
min?: number; // Minimum value (inclusive)
max?: number; // Maximum value (inclusive)
}
// Term Filter - Categorical matching
interface TermFilter {
field: string;
values: any[]; // Values to match
operator?: "AND" | "OR"; // Default: "OR"
}
// Boolean Filter - True/false matching
interface BooleanFilter {
field: string;
value: boolean;
}Sorting Options:
interface SortConfig {
primary: SortOption; // Primary sort field
secondary?: SortOption; // Tie-breaker
keepRelevance?: boolean; // Use relevance as final tie-breaker (default: true)
}
interface SortOption {
field: string;
order: "asc" | "desc";
type?: "number" | "string" | "date"; // Auto-detected if not specified
}Real-World E-Commerce Example:
// Product catalog search
const catalog = [
{ name: "iPhone 15 Pro", category: "Phones", price: 999, rating: 4.8, inStock: true },
{ name: "Samsung Galaxy S24", category: "Phones", price: 899, rating: 4.7, inStock: true },
{ name: "iPad Air", category: "Tablets", price: 599, rating: 4.6, inStock: false },
{ name: "MacBook Pro", category: "Laptops", price: 1999, rating: 4.9, inStock: true },
];
const index = buildFuzzyIndex(catalog, {
fields: ["name", "category", "price", "rating", "inStock"],
});
// User searches "phone" with filters
const results = getSuggestions(index, "phone", 10, {
filters: {
ranges: [{ field: "price", max: 1000 }],
terms: [{ field: "category", values: ["Phones"] }],
booleans: [{ field: "inStock", value: true }]
},
sort: {
primary: { field: "price", order: "asc" }
}
});
// Returns: Samsung Galaxy S24 ($899), iPhone 15 Pro ($999)
// Filtered by: price ≤ $1000, category = Phones, inStock = true
// Sorted by: price ascendingUse Cases:
- 🛒 Product Search - Filter by price, category, brand, availability
- 📱 Marketplace - Sort by price, rating, popularity
- 🏨 Booking Sites - Filter by price range, ratings, availability
- 🍔 Food Delivery - Filter by cuisine, price, rating, delivery time
- 🏠 Real Estate - Filter by price, bedrooms, location
13. Stop Words Filtering
Filter common words that add noise to search results:
import { buildFuzzyIndex, getSuggestions, DEFAULT_STOP_WORDS } from 'fuzzyfindjs';
// Enable stop words filtering
const index = buildFuzzyIndex(dictionary, {
config: {
stopWords: ['the', 'a', 'an', 'is', 'at', 'on', 'in'],
enableStopWords: true
}
});
// Search with automatic filtering
getSuggestions(index, 'the hospital');
// Searches for: "hospital" (stop word "the" removed)
getSuggestions(index, 'a school in the city');
// Searches for: "school city" (stop words filtered)
// Use built-in stop words for any language
const index2 = buildFuzzyIndex(dictionary, {
config: {
stopWords: DEFAULT_STOP_WORDS.english, // 38 common English stop words
enableStopWords: true
}
});
// Available languages
DEFAULT_STOP_WORDS.english // the, a, an, is, at, on, in, to, for...
DEFAULT_STOP_WORDS.german // der, die, das, ein, eine, und, oder...
DEFAULT_STOP_WORDS.spanish // el, la, los, las, un, una, de, en...
DEFAULT_STOP_WORDS.french // le, la, les, un, une, de, à, et...Benefits:
- ✅ Better Quality - Focus on meaningful words
- ✅ Faster Search - Fewer words to process
- ✅ Case Preserved - Original text case maintained
- ✅ Multi-language - Built-in stop words for 4 languages
- ✅ Safe Fallback - Returns original query if all words are stop words
Use Cases:
- 🏥 Medical Search: "the hospital" → "hospital"
- 📚 Document Search: "a guide to programming" → "guide programming"
- 🏢 Business Search: "the company in london" → "company london"
- 🔍 General Search: Remove noise from user queries
Manual Filtering:
import { filterStopWords, isStopWord } from 'fuzzyfindjs';
// Filter stop words from any text
filterStopWords('the quick brown fox', ['the', 'a']);
// → 'quick brown fox'
// Check if a word is a stop word
isStopWord('the', DEFAULT_STOP_WORDS.english);
// → true13. Word Boundaries
Precise matching with word boundary detection and wildcard support:
import { buildFuzzyIndex, getSuggestions } from 'fuzzyfindjs';
// Enable word boundaries for more precise results
const index = buildFuzzyIndex(dictionary, {
config: {
wordBoundaries: true
}
});
// Without word boundaries (default)
const index1 = buildFuzzyIndex(['cat', 'category', 'scatter', 'concatenate']);
getSuggestions(index1, 'cat');
// Matches: "cat", "category", "scatter", "concatenate" (all contain 'cat')
// With word boundaries
const index2 = buildFuzzyIndex(['cat', 'category', 'scatter', 'concatenate'], {
config: { wordBoundaries: true }
});
getSuggestions(index2, 'cat');
// Matches: "cat", "category" (starts with 'cat')
// Doesn't match: "scatter", "concatenate" (cat in middle)
// Wildcard support
getSuggestions(index, 'cat*');
// Matches: "cat", "cats", "category"
getSuggestions(index, 'app*tion');
// Matches: "application", "appreciation"Benefits:
- ✅ More Precise - Reduce false positives
- ✅ Wildcard Support - Flexible pattern matching with
* - ✅ User Control - Choose exact vs substring matching
- ✅ Professional - Standard search engine behavior
- ✅ Backwards Compatible - Disabled by default
Use Cases:
- 🔍 Exact Search: Find "cat" without matching "scatter"
- 📝 Autocomplete: Match word prefixes only
- 🎯 Pattern Matching: Use wildcards for flexible queries
- 📚 Dictionary Search: Match whole words only
Manual Boundary Checking:
import { isWordBoundary, matchesAtWordBoundary, matchesWildcard } from 'fuzzyfindjs';
// Check if position is at word boundary
isWordBoundary('hello world', 6); // → true (after space)
isWordBoundary('hello', 2); // → false (middle of word)
// Check if match is at word boundary
matchesAtWordBoundary('the cat sat', 4, 3); // → true ('cat')
matchesAtWordBoundary('scatter', 1, 3); // → false ('cat' in middle)
// Wildcard matching
matchesWildcard('category', 'cat*'); // → true
matchesWildcard('application', 'app*'); // → true14. Data Indexing Utilities
Easily extract searchable words from unstructured data sources like HTML, JSON, or text dumps:
import { dataToIndex, createFuzzySearch } from 'fuzzyfindjs';
// ✅ Simple text
const text = "The quick brown fox jumps over the lazy dog.";
const words = dataToIndex(text, {
minLength: 3,
stopWords: ['the', 'a', 'an']
});
// → ['quick', 'brown', 'fox', 'jumps', 'over', 'lazy', 'dog']
// ✅ HTML content (strips tags automatically)
const html = `
<html>
<body>
<h1>Welcome to Our Store</h1>
<p>We sell <strong>amazing</strong> products!</p>
</body>
</html>
`;
const htmlWords = dataToIndex(html, { format: 'html' });
// → ['welcome', 'our', 'store', 'we', 'sell', 'amazing', 'products']
// ✅ JSON data (extracts string values only)
const products = [
{ name: 'iPhone 15', category: 'Electronics', price: 999 },
{ name: 'MacBook Pro', category: 'Computers', price: 2499 }
];
const jsonWords = dataToIndex(JSON.stringify(products), { format: 'json' });
// → ['iphone', '15', 'electronics', 'macbook', 'pro', 'computers']
// ✅ Base64 encoded content
const base64 = btoa("Hello World");
const base64Words = dataToIndex(base64, { format: 'base64' });
// → ['hello', 'world']
// ✅ Direct integration with fuzzy search
const htmlContent = "<h1>Coffee</h1><p>Kaffee is German for coffee</p>";
const dictionary = dataToIndex(htmlContent, { format: 'html' });
const search = createFuzzySearch(dictionary);
search.search('kaffee');
// → [{ display: 'kaffee', score: 1.0, ... }]Options:
interface DataToIndexOptions {
minLength?: number; // Minimum word length (default: 2)
splitWords?: boolean; // Split into words (default: true)
stopWords?: string[]; // Remove stop words (default: false)
overlap?: number; // Chunk overlap (default: 0)
chunkSize?: number; // Chunk size (default: 0 = no chunking)
splitOn?: 'word' | 'sentence' | 'paragraph'; // Split strategy
format?: 'string' | 'html' | 