elasticlunr, fuse.js, fuzzy-search, fuzzysearch, and fuzzysort are JavaScript libraries designed to perform approximate string matching directly in the browser or Node.js environment. They allow developers to build search features without relying on a backend server, each using different algorithms like inverted indexes, bit-parallelism, or Levenshtein distance. fuse.js is widely adopted for its balance of features and configuration, while fuzzysort prioritizes raw speed and match highlighting. elasticlunr offers full-text search capabilities similar to search engines, whereas fuzzysearch and fuzzy-search provide lightweight, simpler alternatives for basic needs.
Building search functionality directly in the browser removes server latency but introduces challenges around performance and algorithm selection. The packages elasticlunr, fuse.js, fuzzy-search, fuzzysearch, and fuzzysort all solve approximate string matching, but they approach the problem with different trade-offs in indexing, scoring, and maintenance. Let's examine how they handle real-world engineering requirements.
The way a library handles data indexing dictates its performance ceiling. Some libraries build an inverted index once, while others scan the array on every query.
elasticlunr builds a full inverted index, similar to Lunr.js or Elasticsearch.
// elasticlunr: Build index once
var index = elasticlunr(function () {
this.addField('title');
this.addField('body');
this.setRef('id');
});
docs.forEach(function (doc) { index.add(doc); });
// Query the index
var results = index.search('frontend architecture');
fuse.js creates a search instance that preprocesses the data (in v7+).
elasticlunr but still benefits from initialization.// fuse.js: Initialize with options
const fuse = new Fuse(books, {
keys: ['title', 'author'],
threshold: 0.3
});
// Search the instance
const results = fuse.search('react guide');
fuzzy-search, fuzzysearch, and fuzzysort typically scan the array on every query.
// fuzzysort: Direct search on array
const results = fuzzysort.search('vscode', files, { key: 'name' });
// fuzzysearch: Simple function call
const results = fuzzysearch('query', largeString);
Search relevance is subjective. Some libraries let you tune weights and thresholds, while others offer fixed algorithms.
fuse.js offers the most granular control over scoring.
// fuse.js: Weighted fields
const fuse = new Fuse(items, {
keys: [
{ name: 'title', weight: 0.7 },
{ name: 'tags', weight: 0.3 }
],
threshold: 0.4 // 0.0 is exact, 1.0 matches anything
});
elasticlunr uses term frequency and field length for scoring.
// elasticlunr: Field boosting
var index = elasticlunr(function () {
this.addField('title', { boost: 10 });
this.addField('body');
});
fuzzysort focuses on speed and match quality.
// fuzzysort: Options for limit and threshold
const results = fuzzysort.search('python', commands, {
limit: 10,
threshold: -10000 // Lower is stricter
});
fuzzy-search and fuzzysearch have minimal configuration.
// fuzzy-search: Instantiate with keys
const searcher = new FuzzySearch(list, ['name'], { caseSensitive: false });
const results = searcher.search('query');
Showing users why a result matched is critical for good UX. Some libraries handle this natively, while others require manual work.
fuzzysort has built-in highlighting support.
// fuzzysort: Built-in highlight
const result = fuzzysort.single('bash', { name: 'bashrc' });
const highlighted = fuzzysort.highlight(result, '<b>', '</b>');
// Output: "<b>b</b><b>a</b><b>s</b><b>h</b>rc"
fuse.js requires enabling a specific option to get match details.
includeMatches: true.// fuse.js: Enable matches
const fuse = new Fuse(items, { includeMatches: true });
const results = fuse.search('query');
// Manual highlight logic required
results.forEach(result => {
result.matches.forEach(match => {
// Apply highlighting based on match.indices
});
});
elasticlunr, fuzzy-search, and fuzzysearch do not provide highlighting.
// elasticlunr: Manual highlighting
// No native API for highlight indices
function highlight(text, query) {
const regex = new RegExp(`(${query})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
Performance depends heavily on dataset size and whether an index is used.
elasticlunr scales best for large, static datasets.
fuse.js handles medium datasets well.
fuzzysort is optimized for raw speed on unindexed data.
fuzzy-search and fuzzysearch are for small lists.
Long-term viability is a key architectural consideration.
fuse.js is actively maintained with regular updates.
elasticlunr has seen reduced activity recently.
lunr.js which is more active.lunr.js if boolean search is not strictly required.fuzzysort is stable and maintained.
fuzzy-search and fuzzysearch are stable but simple.
| Feature | elasticlunr | fuse.js | fuzzy-search | fuzzysearch | fuzzysort |
|---|---|---|---|---|---|
| Indexing | โ Inverted Index | โ ๏ธ Preprocessed | โ None | โ None | โ None |
| Highlighting | โ Manual | โ ๏ธ Indices Only | โ Manual | โ Manual | โ Built-in |
| Weights | โ Field Boost | โ Granular | โ Basic | โ None | โ Basic |
| Boolean Logic | โ AND/OR/NOT | โ No | โ No | โ No | โ No |
| Best Scale | Large (10k+) | Medium (1k-5k) | Small (<1k) | Small (<1k) | Medium (1k-5k) |
| Maintenance | โ ๏ธ Low | โ High | โ Stable | โ Stable | โ Stable |
fuse.js is the general-purpose workhorse ๐ด โ it balances features, configuration, and community support better than any other option. Use it for most standard search requirements like contact lists, product filters, or documentation search.
fuzzysort is the speed specialist ๐๏ธ โ choose it when you need millisecond-level response times and built-in highlighting, such as in command palettes or IDE-like interfaces.
elasticlunr is the power user tool ๐ ๏ธ โ it shines when you need boolean logic and field boosting on large static datasets, but verify its maintenance status fits your project timeline.
fuzzy-search and fuzzysearch are the lightweight utilities ๐ชถ โ use them for simple, low-stakes filtering where adding a heavier dependency is not justified.
Final Thought: For most professional frontend applications, fuse.js offers the safest balance of power and support. However, if your specific need is high-performance autocomplete with highlighting, fuzzysort is technically superior for that narrow case. Always measure performance against your actual dataset size before finalizing your choice.
Choose elasticlunr if you need full-text search capabilities with boolean logic (AND, OR, NOT) and field boosting similar to a search engine. It is best suited for larger datasets where building an inverted index provides significant query speed benefits. However, be aware that maintenance has slowed, so evaluate long-term support risks before committing to it for critical infrastructure.
Choose fuse.js if you need a highly configurable search engine with weighted scoring, field-specific search, and a large community ecosystem. It is ideal for applications requiring a balance between search accuracy and ease of setup, such as documentation sites or contact lists. The library handles complex matching rules well, making it a safe default for most professional projects.
Choose fuzzy-search if you want a straightforward, no-frills implementation for small lists where configuration overhead is unnecessary. It works well for simple UI filters where users expect basic typo tolerance without complex ranking logic. This package is suitable for quick prototypes or internal tools where search depth is not a priority.
Choose fuzzysearch if bundle size is your primary concern and you only need basic Levenshtein distance matching. It is extremely lightweight and works as a single function, making it perfect for embedding in constrained environments or micro-frontends. Avoid this for large datasets as it lacks indexing and will scan every item on each query.
Choose fuzzysort if performance is critical and you need built-in match highlighting for search results. It is optimized for speed and provides tools to wrap matched characters in HTML tags directly. This is the best choice for real-time search inputs where latency must be minimized and visual feedback on matches is required.
Elasticlunr.js is a lightweight full-text search engine developed in JavaScript for browser search and offline search. Elasticlunr.js is developed based on Lunr.js, but more flexible than lunr.js. Elasticlunr.js provides Query-Time boosting, field search, more rational scoring/ranking methodology, fast computation speed and so on. Elasticlunr.js is a bit like Solr, but much smaller and not as bright, but also provide flexible configuration, query-time boosting, field search and other features.
A very simple search index can be created using the following scripts:
var index = elasticlunr(function () {
this.addField('title');
this.addField('body');
this.setRef('id');
});
Adding documents to the index is as simple as:
var doc1 = {
"id": 1,
"title": "Oracle released its latest database Oracle 12g",
"body": "Yestaday Oracle has released its new database Oracle 12g, this would make more money for this company and lead to a nice profit report of annual year."
}
var doc2 = {
"id": 2,
"title": "Oracle released its profit report of 2015",
"body": "As expected, Oracle released its profit report of 2015, during the good sales of database and hardware, Oracle's profit of 2015 reached 12.5 Billion."
}
index.addDoc(doc1);
index.addDoc(doc2);
Then searching is as simple:
index.search("Oracle database profit");
Also, you could do query-time boosting by passing in a configuration.
index.search("Oracle database profit", {
fields: {
title: {boost: 2},
body: {boost: 1}
}
});
This returns a list of matching documents with a score of how closely they match the search query:
[{
"ref": 1,
"score": 0.5376053707962494
},
{
"ref": 2,
"score": 0.5237481076838757
}]
If user do not want to store the original JSON documents, they could use the following setting:
var index = elasticlunr(function () {
this.addField('title');
this.addField('body');
this.setRef('id');
this.saveDocument(false);
});
Then elasticlunr.js will not store the JSON documents, this will reduce the index size, but also bring some inconvenience such as update a document or delete a document by document id or reference. Actually most of the time user will not udpate or delete a document from index.
API documentation is available, as well as a full working example.
Elasticlunr.js is developed based on Lunr.js, but more flexible than lunr.js. Elasticlunr.js provides Query-Time Boosting, Field Search, more rational scoring/ranking methodology, flexible configuration and so on. A bit like Solr, but much smaller and not as bright, but also provide flexible configuration, query-time boosting, field search, etc.
Simply include the elasticlunr.js source file in the page that you want to use it. Elasticlunr.js is supported in all modern browsers.
Browsers that do not support ES5 will require a JavaScript shim for Elasticlunr.js to work. You can either use Augment.js, ES5-Shim or any library that patches old browsers to provide an ES5 compatible JavaScript environment.
This part only contain important apects of elasticlunr.js, for the whole documentation, please go to API documentation.
When you first create a index instance, you need to specify which field you want to index. If you did not specify which field to index, then no field will be searchable for your documents. You could specify fields by:
var index = elasticlunr(function () {
this.addField('title');
this.addField('body');
this.setRef('id');
});
You could also set the document reference by this.setRef('id'), if you did not set document ref, elasticlunr.js will use 'id' as default.
You could do the above index setup as followings:
var index = elasticlunr();
index.addField('title');
index.addField('body');
index.setRef('id');
Also you could choose not store the original JSON document to reduce the index size by:
var index = elasticlunr();
index.addField('title');
index.addField('body');
index.setRef('id');
index.saveDocument(false);
Add document to index is very simple, just prepare you document in JSON format, then add it to index.
var doc1 = {
"id": 1,
"title": "Oracle released its latest database Oracle 12g",
"body": "Yestaday Oracle has released its new database Oracle 12g, this would make more money for this company and lead to a nice profit report of annual year."
}
var doc2 = {
"id": 2,
"title": "Oracle released its profit report of 2015",
"body": "As expected, Oracle released its profit report of 2015, during the good sales of database and hardware, Oracle's profit of 2015 reached 12.5 Billion."
}
index.addDoc(doc1);
index.addDoc(doc2);
If your JSON document contains field that not configured in index, then that field will not be indexed, which means that field is not searchable.
Elasticlunr.js support remove a document from index, just provide JSON document to elasticlunr.Index.prototype.removeDoc() function.
For example:
var doc = {
"id": 1,
"title": "Oracle released its latest database Oracle 12g",
"body": "Yestaday Oracle has released its new database Oracle 12g, this would make more money for this company and lead to a nice profit report of annual year."
}
index.removeDoc(doc);
Remove a document will remove each token of that document's each field from field-specified inverted index.
Elasticlunr.js support update a document in index, just provide JSON document to elasticlunr.Index.prototype.update() function.
For example:
var doc = {
"id": 1,
"title": "Oracle released its latest database Oracle 12g",
"body": "Yestaday Oracle has released its new database Oracle 12g, this would make more money for this company and lead to a nice profit report of annual year."
}
index.update(doc);
Elasticlunr.js provides flexible query configuration, supports query-time boosting and Boolean logic setting. You could setup a configuration tell elasticlunr.js how to do query-time boosting, which field to search in, how to do the boolean logic. Or you could just use it by simply provide a query string, this will aslo works perfectly because the scoring mechanism is very efficient.
Because elasticlunr.js has a very perfect scoring mechanism, so for most of your requirement, simple search would be easy to meet your requirement.
index.search("Oracle database profit");
Output is a results array, each element of results array is an Object contain a ref field and a score field.
ref is the document reference.
score is the similarity measurement.
Results array is sorted descent by score.
Setup which fields to search in by passing in a JSON configuration, and setup boosting for each search field. If you setup this configuration, then elasticlunr.js will only search the query string in the specified fields with boosting weight.
The scoring mechanism used in elasticlunr.js is very complex, please goto details for more information.
index.search("Oracle database profit", {
fields: {
title: {boost: 2},
body: {boost: 1}
}
});
Elasticlunr.js also support boolean logic setting, if no boolean logic is setted, elasticlunr.js use "OR" logic defaulty. By "OR" default logic, elasticlunr.js could reach a high Recall.
index.search("Oracle database profit", {
fields: {
title: {boost: 2},
body: {boost: 1}
},
bool: "OR"
});
Boolean model could be setted by global level such as the above setting or it could be setted by field level, if both global and field level contains a "bool" setting, field level setting will overwrite the global setting.
index.search("Oracle database profit", {
fields: {
title: {boost: 2, bool: "AND"},
body: {boost: 1}
},
bool: "OR"
});
The above setting will search title field by AND model and other fields by "OR" model.
Currently if you search in multiply fields, resutls from each field will be merged together to give the query results. In the future elasticlunr will support configuration that user could set how to combine the results from each field, such as "most_field" or "top_field".
Sometimes user want to expand a query token to increase RECALL, then user could set expand model to true by configuration, default is false. For example, user query token is "micro", and assume "microwave" and "microscope" are in the index, then is user choose expand the query token "micro" to increase RECALL, both "microwave" and "microscope" will be returned and search in the index. The query results from expanded tokens are penalized because they are not exactly the same as the query token.
index.search("micro", {
fields: {
title: {boost: 2, bool: "AND"},
body: {boost: 1}
},
bool: "OR",
expand: true
});
Field level expand configuration will overwrite global expand configuration.
index.search("micro", {
fields: {
title: {
boost: 2,
bool: "AND",
expand: false
},
body: {boost: 1}
},
bool: "OR",
expand: true
});
Elasticlunr.js contains some default stop words of English, such as:
Defaultly elasticlunr.js contains 120 stop words, user could decide not use these default stop words or add customized stop words.
You could remove default stop words simply as:
elasticlunr.clearStopWords();
User could add a list of customized stop words.
var customized_stop_words = ['an', 'hello', 'xyzabc'];
elasticlunr.addStopWords(customized_stop_words);
Elasticlunr support Node.js, you could use elastilunr in node.js as a node-module.
Install elasticlunr by:
npm install elasticlunr
then in your node.js project or in node.js console:
var elasticlunr = require('elasticlunr');
var index = elasticlunr(function () {
this.addField('title')
this.addField('body')
});
var doc1 = {
"id": 1,
"title": "Oracle released its latest database Oracle 12g",
"body": "Yestaday Oracle has released its new database Oracle 12g, this would make more money for this company and lead to a nice profit report of annual year."
}
var doc2 = {
"id": 2,
"title": "Oracle released its profit report of 2015",
"body": "As expected, Oracle released its profit report of 2015, during the good sales of database and hardware, Oracle's profit of 2015 reached 12.5 Billion."
}
index.addDoc(doc1);
index.addDoc(doc2);
index.search("Oracle database profit");
Default supported language of elasticlunr.js is English, if you want to use elasticlunr.js to index other language documents, then you need to use elasticlunr.js combined with lunr-languages.
Suppose you are using elasticlunr.js in browser for other languages, you could download the corresponding language support from lunr-languages, then include the scripts as:
<script src="lunr.stemmer.support.js"></script>
<script src="lunr.de.js"></script>
then, you could use elasticlunr.js as normal:
var index = elasticlunr(function () {
// use the language (de)
this.use(elasticlunr.de);
// then, the normal elasticlunr index initialization
this.addField('title')
this.addField('body')
});
Pay attention to the special code:
this.use(elasticlunr.de);
If you are using other language, such as es(Spanish), download the corresponding lunr.es.js file and lunr.stemmer.support.js, and change the above line to:
this.use(elasticlunr.es);
Suppose you are using elasticlunr.js in Node.js for other languages, you could download the corresponding language support from lunr-languages, put the files lunr.es.js file and lunr.stemmer.support.js in your project, then in your Node.js module, use elasticlunr.js as:
var elasticlunr = require('elasticlunr');
require('./lunr.stemmer.support.js')(elasticlunr);
require('./lunr.de.js')(elasticlunr);
var index = elasticlunr(function () {
// use the language (de)
this.use(elasticlunr.de);
// then, the normal elasticlunr index initialization
this.addField('title')
this.addField('body')
});
For more details, please go to lunr-languages.
See the CONTRIBUTING.mdown file.