Search can be the critical difference between a good app and a great app. Although search technologies like Elasticsearch, SOLR, RedisSearch, and more options have become readily available, they still require a non-trivial amount of resources to operate and maintain. The web community has a search solution for web developers in Lunr, and luckily .NET OSS community has ported the library to a NuGet package.
This post will explore what Lunr is and how we can use Lunr-Core to provide simple yet powerful search experiences for our users.
What Is Lunr?
Lunr draws inspiration from SOLR, a JAVA-based search engine platform built on Apache Lucene. Admittedly Lunr is not a replacement for SOLR; instead, Lunr creators designed the library to be small and lightweight, and free of external dependencies. The design philosophy of Lunr allows developers to use it in an array of scenarios not possible for the more robust solutions.
Lunr boasts standard search functionality such as indexing fields, tokenizers, stop words, scoring, and a document processing pipeline that allows for features like:
- Full-text search support for 14 languages
- Boost terms at query time or boost entire documents at index time
- Scope searches to specific fields
- Fuzzy term matching with wildcards or edit distance
While this all may sound complicated, the API is straightforward. Let’s take a look at the JavaScript implementation first.
This indexing process has two search fields of title
and body
. Once we’ve built the index, we can search for values using the idx
instance.
Lunr returns search results in a JSON array.
JavaScript is great and all, but we’re .NET developers! What about .NET?!
Enter Lunr-Core For .NET Search
Lunr-Core is a port of Lunr for use in .NET applications and has the fantastic benefit of being 100% compatible with Lunr. That means we can use indexes built with either the JavaScript implementation or the .NET implementation. To get started with Lunr-Core, we’ll need to install the NuGet package.
The C# API is very similar to its JavaScript counterpart. Let’s look at indexing a document.
Once we build our index, we can use the index
variable to perform searches.
Let’s consider the limitations of Lunr before jumping to a complete .NET sample.
Considerations When Using Lunr
Lunr is a fully-featured search engine library, but there are drawbacks .NET developers should consider before using Lunr.
The first drawback is Lunr lacks incremental index builds. That means adding a single document will require complete indexing of all index entries.
Lunr indexes are now immutable. Once they have been built, it is not possible to add, update or remove any documents in the index. –Lunr Docs
If our data is changing rapidly, then Lunr might not be the best choice of search technology.
Another drawback is that Lunr writes indexes to JSON, an unoptimized disk format that may be surprisingly larger than the original data. For example, an indexed CSV that’s originally 2.6MB results in an index file that is 17.3 MB on disk in the upcoming sample. If reading and writing from a disk is an expensive operation, Lunr might not be the right choice for our use case.
A Lunr-Core C# Sample
So if you’re still interested in using Lunr for your search experience, then I’ve provided a sample below. We’ll be reading U.S. cities from a CSV and indexing them. We’ll also be writing our index to disk to eliminate the cost of indexing our documents at startup.
For folks who want to run this sample locally, I’ve provided all the source code on my GitHub repository.
Conclusion
Lunr and, by extension, Lunr-Core are excellent for providing search experiences for mostly static datasets. It’s also a perfect option for folks building client experiences, especially as Web Assembly brings the .NET to the browser. As you saw in this post, it doesn’t take much to start providing a compelling search experience to your users. Lunr is also a great starting point to upgrade to one of the more robust solutions previously mentioned.
I hope you found this post helpful, and thank you again for reading.