Rate this tutorial
When you're developing a web application, a quality user experience can make or break your application. A common application feature is to allow users to enter text into a search bar to find a specific piece of information. Rather than having the user enter information and hope it's valid, you can help your users find what they are looking for by offering autocomplete suggestions as they type.
So what could go wrong?
If your users are like me, they'll make multiple spelling mistakes for every one word of text. If you're creating an autocomplete field using regular expressions on your data, programming to account for misspellings and fat fingers is tough!
In this tutorial, we're going to see how to create a simple web application that surfaces autocomplete suggestions to the user. These suggestions can be easily created using the full-text search features available in Atlas Search.
To get a better idea of what we want to accomplish, have a look at the following animated image:
In the above image you'll notice that I not only made spelling mistakes, but I also made use of a word that appeared anywhere within the field for any document in a collection.
We'll skip the basics of configuring Node.js or MongoDB and assume that you already have a few things installed, configured, and ready to go:
- MongoDB Atlas with an M0 cluster or better, with user and network safe-list configurations established
- Node.js installed and configured
- A food database with a recipes collection established
We'll be using Atlas Search within MongoDB Atlas. To follow this tutorial, the recipes collection (within the food database) will expect documents that look like this:
Make sure to create many documents within your recipes collection, some of which with similar names. In my example, I used "grilled cheese", "five cheese lasagna", and "baked salmon".
Before we start creating a frontend or backend, we need to prepare our collection for search by creating a special search index.
Within the Collections tab of your cluster, find the recipes collection and then choose the Search Indexes tab.
You probably won't have an Atlas Search index created yet, so we'll need to create one.
By default, Atlas Search dynamically maps every field in a collection. That means every field in our document will be checked against our search terms. This is great for growing collections where the schema may evolve, and you want to search through many different fields. However it can be resource intensive, as well. For our app, we actually just want to search by one particular field, the "name" field in our recipe documents. To do that, choose "Create Search Index" and change the code to the following:
In the above example, we're creating an index on the
namefield within our documents using an autocomplete index. Any fields that aren't explicitly mapped, like the
ingredientsarray, will not be searched.
For this example, we can paste the JSON using the JSON Editor.
Now, click "Create Index". That's it! Just give MongoDB Atlas a few minutes to create your search index.
At this point in time, we should have our data collection of recipes, as well as an Atlas Search index created on that data for the
namefield. We're now ready to create a backend that will interact with our data using the MongoDB Node.js driver.
On your computer, create a new project directory with a main.js file within that directory. Using the command line, execute the following commands:
The above commands will initialize the package.json file and install each of our project dependencies for creating a RESTful API that interacts with MongoDB.
Within the main.js file, add the following code:
Remember when I said I'd be brushing over the getting started with MongoDB stuff? I meant it, but, if you're copying and pasting the above code, make sure you replace the following line in your code:
I store my MongoDB Atlas information in an environment variable rather than hard-coding it into the application. If you wish to do the same, create an environment variable on your computer called
ATLAS_URIand set it to your MongoDB connection string. This connection string will look something like this:
What we're interested in for this example are the
/get/:idendpoints for the RESTful API. The first endpoint will leverage Atlas Search, while the second endpoint will get the document based on its
_idvalue. This is useful in case you want to search for documents and then get all the information about the selected document.
So let's expand upon the endpoint for searching:
In the above code, we are creating an aggregation pipeline with a single
$searchstage, which will be powered by our Atlas Search index. It will use the user-provided data as the query and the
autocompleteoperator to give them that type-ahead experience. In a production scenario we might want to do further validation on the user provided data, but it's fine for this example. Also note that when using Atlas Search, an aggregation pipeline is required, and the
$searchoperator must be the first stage of that pipeline.
namerepresents the field within our documents that we want to search in. Remember,
nameis also the field we defined in our index.
This is where the fun stuff comes in!
We're doing a fuzzy search. This means we're finding strings which are similar, but not necessarily exactly the same, to the search term. Remember when I misspelled
maxEditsfield represents how many consecutive characters must match. In my example there was only one, but what if I misspelled it as
azcharacters are not correct?
prefixLengthfield indicates the number of characters at the beginning of each term in the result that must match exactly. In our example, three characters at the beginning of each term must match.
This is all very powerful considering what kind of mess your code would look like by using regular expressions or
So let's take care of our other endpoint:
The above code is nothing fancy. We're taking an id hash that the user provides, converting it into a proper object id, and then finding a single document.
Now that we have a backend to work with, we can take care of the frontend that improves the overall user experience. There are plenty of ways to create an autocomplete form, but for this example jQuery will be doing the heavy lifting.
Create a new project with an index.html file in it. Open that file and include the following:
The above markup doesn't do much of anything. We're just importing the jQuery dependencies and defining the form element that will show the autocomplete. After clicking the autocomplete element, the data will populate our list of ingredients in the list.
<script>tag we can add the following:
A few things are happening in the above code.
autocompletefunction, we define a
sourcefor where our data comes from and a
selectfor what happens when we select something from our list. We also define a
minLengthso that we aren't hammering our backend and database with every keystroke.
If we take a closer look at the
sourcefunction, we have the following:
selectfunction, we can further analyze what's happening:
We are making a second request to our other API endpoint. We are then flushing the list of ingredients on our page and repopulating them with the new ingredients.
When running this application, make sure the backend is running as well, otherwise your frontend will have nothing to communicate to.
To serve the frontend application, there are a few options not limited to what's below:
- Host the code on MongoDB Realm or another hosting service.
- Use Python to create a
My personal favorite is to use the
servepackage. If you install it, you can execute
servefrom your command line within the working directory of your project.
You just saw how to leverage MongoDB Atlas Search to suggest data to users in an autocomplete form. Atlas Search is great because it uses natural language to search within document fields to spare you from having to write long and complicated regular expressions or application logic.
Don't forget that we did our search by using the
$searchoperator within an aggregation pipeline. This means you could add other stages to your pipeline to do some really extravagant things. For example, after the
$searchpipeline stage, you could use an
$inon the ingredients array to limit the results to only chocolate recipes. Also, you can make use of other neat operators within the
$searchstage, beyond the
autocompleteoperator. For example, you could make use of the
nearoperator for numerical and geospatial search, or operators such as
wildcardfor other tasks. More information on these operators can be found in the