Build an Autocomplete widget with React and Elastic Search
This post is a compilation of opinionated technical decisions related to building an auto-suggest search widget with the following requirements:
- Build a search bar to quickly search for a customer by first or last name or a unique short code.
- Should support type-ahead search with results being refined with more keystrokes
- Search should be possible by first, middle or last name or full name.
- If there are two or more customers with same name, order them by their created time with the most recent one at the top
There are two parts to building this Autocomplete widget. Let’s talk about the design for the backend first.
Tip: When working with React components, you can use Bit to easily organize and share components across projects and team members, and build faster.
ElasticSearch API
Though many of the RDBMs already support full-text search, we would prefer ElasticSearch essentially for its superior scoring algorithm and performance. There are two ways to build the ElasticSearch index to power our auto-suggest functionality:
- Completion Suggester
The completion suggester is a so-called prefix suggester. It does not do spell correction like the term
or phrase
suggesters but allows basic auto-complete
functionality. If performance is the primary concern, it is recommended that you go with completion suggester.
2. Edge n-grams Tokenizer
Reading through our requirements, the results should show up even if the user types part of the middle of the customer’s name. This is not possible with completion suggester, so let’s use the NGram Tokenizer.
If the user types
chandler
ormuriel
orbing
orchand bing
we are suppose to return Chandler Muriel Bing as the result!
The edge_ngram tokenizer first breaks a name down into words whenever it encounters one of a list of specified characters, then it emits N-grams of each word where the start of the N-gram is anchored to the beginning of the word. This is perfect when the index will have to match when full or partial keywords from the name are entered.
If you are interested in spinning up a local docker image to boot up elastic search to play with the code in this blog, go here. The code here complies with ES v.6.4 which is the LTS version at the time of writing this blog.
Let’s go ahead and create our index with name and short_code fields:
Read through the Edge NGram docs to know more about min_gram
and max_gram
parameters.
Also note that, we create a single field called fullName
to merge the customer’s first and last names. Storing the name together as one field offers us a lot of flexibility in terms on analyzing as well querying.
Assuming we have the above customer data as sample, we can insert them into the Elastic Search index using the following command:
Repeat the above command for all customer records and we can start testing our index against our auto-suggest use cases:
Now, let’s start building our query API one step at a time.
The above query will return two records Ross and Monica. Let’s sort them by created date. But, we don’t want to skew order by only listing most recent items at the top pushing relevant results to bottom. So, the right way to do that would be sort by score and then relevancy
Remember for that for each record, based on the match query fired, elastic search assigns a _score
based on relevancy of match and sorting by that gives us the most accurate search results.
Now, the last requirement to add to our implementation is to search by fullName
as well as shortCode
to make the query work for both input fields.
The multi_match query matches the given query keyword against both the fields and returns the result accordingly.
React UI for auto-suggesting
Let’s catch a quick glimpse of the UI widget that we are building so that it is easy for you to visualize and follow:
The main AutoComplete widget where the most of the action happens is listed below.
Quickly walking through the code:
- we use react-autosuggest to make the core suggestion feature work
- axios to make the REST api call to elastic search
- as the user types, we would like to wait on keystrokes and then call elastic search to make the user experience smooth and not call the search api for every word cumulatively typed. We use
debounce
functiononSuggestionFetchRequested
function to achieve that - the
renderSuggestion
helper will allow you to design the search result items as you wish to show. We have included the customer’s name and short code in the result
The code for the complete UI project is available here for reference: https://github.com/rcdexta/react-autocomplete-demo
Note that the UI app and ElasticSearch are running on different ports, so by default you will get a CORS error on the browser. For the sake of testing and development, use the following docker command to bypass it:
$ docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e "http.cors.enabled=true" -e "http.cors.allow-origin=*" docker.elastic.co/elasticsearch/elasticsearch:6.4.0
If you find the blog post useful, follow this blog, follow me on Twitter and please feel free to comment and ask anything below! thanks :)