Adding Autocomplete To Your NextJS Applications With Atlas Search
Abdulrasaq Jamiu Adewuyi11 min read • Published Feb 28, 2023 • Updated Feb 28, 2023
Rate this tutorial
Imagine landing on a webpage with thousands of items and you have to scroll through all of them to get what you are looking for. You will agree that it's a bad user experience. For such a website, the user might have to leave for an alternative website, which I'm sure is not what any website owner would want.
Providing users with an excellent search experience, such that they can easily search for what they want to see, is crucial for giving them a top-notch user experience.
The easiest way to incorporate rich, fast, and relevant searches into your applications is through MongoDB Atlas Search, a component of MongoDB Atlas.
In this guide, I will be showing you how I created a text search for a home rental website and utilize Atlas Search to integrate full-text search functionality, also incorporating autocomplete into the search box.
This search will give users the ability to search for homes by country.
Let's look at the technology we will be using in this project.
If you'd like to follow along, here's what I'll be using.
NextJS: We will be using NextJS to build our front end. NextJS is a popular JavaScript framework for building server-rendered React applications.
I chose this framework because it provides a simple setup and helps with optimizations such as automatic code splitting and optimized performance for faster load times. Additionally, it has a strong community and ecosystem, with a large number of plugins and examples available, making it an excellent choice for building both small- and large-scale web applications.
NodeJS and ExpressJS: We will be using these to build our back end. Both are used together for building server-side web applications.
I chose these frameworks because Node.js is an open-source, cross-platform JavaScript runtime environment for building fast, scalable, and high-performance server-side applications. Express.js, on the other hand, is a popular minimal and flexible Node.js web application framework that provides a robust set of features for building web and mobile applications.
MongoDB Atlas is a fully managed cloud database service provided by MongoDB. It's a cloud-hosted version of the popular NoSQL database (MongoDB) and offers automatic scalability, high availability, and built-in security features. With MongoDB Atlas, developers can focus on building their applications rather than managing the underlying infrastructure, as the service takes care of database setup, operation, and maintenance.
MongoDB Atlas Search is a full-text search and analytics engine integrated with MongoDB Atlas. It enables developers to add search functionality to their applications by providing fast and relevant search results, including text search and faceted search, and it also supports autocomplete and geospatial search.
MongoDB Atlas Search is designed to be highly scalable and easy to use.
Let's get to work!
1 git clone https://github.com/mongodb-developer/search-nextjs/ 2 cd search-nextjs
Once the clone is completed, in this repository, you will see two sub-folders:
mdbsearch
: Contains the Nextjs project (front end)
backend
: Contains the Node.js project (back end)Open the project with your preferred text editor. With this done, let's set up our MongoDB environment.
To set up our MongoDB environment, we will need to follow the below instructions from the MongoDB official documentation.
Your connection string should look like this: mongodb+srv://user:password@cluster0.xxxxx.mongodb.net
We will be working with the
sample-airbnb
sample data from MongoDB for this application because it contains appropriate entries for the project.If you complete the above steps, you should have the sample data loaded in your cluster. Otherwise, check out the documentation on how to load sample data.
The API for our front end will be provided by the Node.js back end. To establish a connection to your database, let's create a
.env
file and update it with the connection string.1 cd backend 2 npm install 3 touch .env
Update .env as below
1 PORT=5050 2 MONGODB_URI=<CONNECTION_STRING>
To start the server, we can either utilize the node executable or, for ease during the development process, use
nodemon
. This tool can automatically refresh your server upon detecting modifications made to the source code. For further information on tool installation, visit the official website.Run the below code
1 npx nodemon .
This command will start the server. You should see a message in your console confirming that the server is running and the database is connected.
With the back end running, let's start the front end of your application. Open a new terminal window and navigate to the
mdbsearch
folder. Then, install all the necessary dependencies for this project and initiate the project by running the npm command. Let's also create a .env
file and update it with the backend url.1 cd ../mdbsearch 2 npm install 3 touch .env
Create a .env file, and update as below:
1 NEXT_PUBLIC_BASE_URL=http://localhost:5050/
Start the application by running the below command.
1 npm run dev
Once the application starts running, you should see the page below running at http://localhost:3000. The running back end is already connected to our front end, hence, during the course of this implementation, we need to make a few modifications to our code.
With this data loading from the MongoDB database, next, let's proceed to implement the search functionality.
To be able to search through data in our collection, we need to follow the below steps:
The MongoDB free tier account gives us the room to create, at most, three search indexes.
From the previously created cluster, click on the Browse collections button, navigate to Search, and at the right side of the search page, click on the Create index button. On this screen, click Next to use the visual editor, add an index name (in our case,
search_home
), select the listingsAndReviews
collection from the sample_airbnb
database, and click Next.From this screen, click on Refine Your Index. Here is where we will specify the field in our collection that will be used to generate search results. In our case ---
address
and property_type
--- address
field is an object that has a country
property, which is our target.Therefore, on this screen, we need to toggle off the Enable Dynamic Mapping option. Under Field Mapping, click the Add Field Mapping button. In the Field Name input, type
address.country
, and in the Data Type, make sure String is selected. Then, scroll to the bottom of the dialog and click the Add button. Create another Field Mapping for property_type
. Data Type should also be String.The index configuration should be as shown below.
At this point, scroll to the bottom of the screen and click on Save Changes. Then, click the Create Search Index button. Then wait while MongoDB creates your search index. It usually takes a few seconds to be active. Once active, we can start querying our collection with this index.
MongoDB provides a search tester, which can be used to test our search indexes before integrating them into our application. To test our search index, let's click the QUERY button in the search index. This will take us to the Search Tester screen.
Remember, we configure our search index to return results from
address.country
or property_type
. So, you can test with values like spain
, brazil
, apartment
, etc. These values will return results, and we can explore each result document to see where the result is found from these fields.Test with values like
span
and brasil
. These will return no data result because it's not an exact match. MongoDB understands that scenarios like these are likely to happen. So, Atlas Search has a fuzzy matching feature. With fuzzy matching, the search tool will be searching for not only exact matching keywords but also for matches that might have slight variations, which we will be using in this project. You can find the details on fuzzy search in the documentation.With the search index created and tested, we can now implement it in our application. But before that, we need to understand what a MongoDB aggregation pipeline is.
Now that we have the search index configured, let's try to integrate it into the API used for this project. Open
backend/index.js
file, find the comment Search endpoint goes here , and update it with the below code.Start by creating the route needed by our front end.
1 // Search endpoint goes here 2 app.get("/search/:search", async (req, res) => { 3 const queries = JSON.parse(req.params.search) 4 // Aggregation pipeline goes here 5 });
In this endpoint,
/search/:search
we create a two-stage aggregation pipeline: $search
and $project
. $search
uses the index search_home
, which we created earlier. The $search
stage structure will be based on the query parameter that will be sent from the front end while the $project
stage simply returns needed fields from the $search
result.This endpoint will receive the
country
and property_type
, so we can start building the aggregation pipeline. There will always be a category property. We can start by adding this.1 // Start building the search aggregation stage 2 let searcher_aggregate = { 3 "$search": { 4 "index": 'search_home', 5 "compound": { 6 "must": [ 7 // get home where queries.category is property_type 8 { "text": { 9 "query": queries.category, 10 "path": 'property_type', 11 "fuzzy": {} 12 }}, 13 14 // get home where queries.country is address.country 15 {"text": { 16 "query": queries.country, 17 "path": 'address.country', 18 "fuzzy": {} 19 }} 20 ]} 21 } 22 };
We don't necessarily want to send all the fields back to the frontend, so we can use a projection stage to limit the data we send over.
1 app.get("/search/:search", async (req, res) => { 2 const queries = JSON.parse(req.params.search) 3 // Start building the search aggregation stage 4 let searcher_aggregate = { ... }; 5 // A projection will help us return only the required fields 6 let projection = { 7 '$project': { 8 'accommodates': 1, 9 'price': 1, 10 'property_type': 1, 11 'name': 1, 12 'description': 1, 13 'host': 1, 14 'address': 1, 15 'images': 1, 16 "review_scores": 1 17 } 18 }; 19 });
Finally, we can use the
aggregate
method to run this aggregation pipeline, and return the first 50 results to the front end.1 app.get("/search/:search", async (req, res) => { 2 const queries = JSON.parse(req.params.search) 3 // Start building the search aggregation stage 4 let searcher_aggregate = { ... }; 5 // A projection will help us return only the required fields 6 let projection = { ... }; 7 // We can now execute the aggregation pipeline, and return the first 50 elements 8 let results = await itemCollection.aggregate([ searcher_aggregate, projection ]).limit(50).toArray(); 9 res.send(results).status(200); 10 });
The result of the pipeline will be returned when a request is made to
/search/:search
.At this point, we have an endpoint that can be used to search for homes by their country.
From our project folder, open the
mdbsearch/components/Header/index.js
file.Find the searchNow
function and update it with the below code.1 //Search function goes here 2 const searchNow = async (e) => { 3 setshow(false) 4 let search_params = JSON.stringify({ 5 country: country, 6 category: `${activeCategory}` 7 }) 8 setLoading(true) 9 await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}search/${search_params}`) 10 .then((response) => response.json()) 11 .then(async (res) => { 12 updateCategory(activeCategory, res) 13 router.query = { country, category: activeCategory }; 14 setcountryValue(country); 15 router.push(router); 16 }) 17 .catch((err) => console.log(err)) 18 .finally(() => setLoading(false)) 19 }
NextFind the
handleChange
function, let update it with the below code 1 const handleChange = async (e) => { 2 //Autocomplete function goes here 3 setCountry(e.target.value); 4 }
With the above update, let's explore our application. Start the application by running
npm run dev
in the terminal. Once the page is loaded, choose a property type, and then click on "search country." At the top search bar, type brazil
. Finally, click the search button. You should see the result as shown below.The search result shows data where
address.country
is brazil and property_type
is apartment. Explore the search with values such as braz, brzl, bral, etc., and we will still get results because of the fuzzy matching feature.Now, we can say the experience on the website is good. However, we can still make it better by adding an autocomplete feature to the search functionality.
Most modern search engines commonly include an autocomplete dropdown that provides suggestions as you type. Users prefer to quickly find the correct match instead of browsing through an endless list of possibilities. This section will demonstrate how to utilize Atlas Search autocomplete capabilities to implement this feature in our search box.
In our case, we are expecting to see suggestions of countries as we type into the country search input. To implement this, we need to create another search index.
From the previously created cluster, click on the Browse collections button and navigate to Search. At the right side of the search page, click on the Create index button. On this screen, click Next to use the visual editor, add an index name (in our case,
country_autocomplete
), select the listingsAndReviews collection from the sample_airbnb database, and click Next.From this screen, click on Refine Your Index. We need to toggle off the Enable Dynamic Mapping option.
Under Field Mapping, click the Add Field Mapping button. In the Field Name input, type
address.country
, and in the Data Type, this time, make sure Autocomplete is selected. Then scroll to the bottom of the dialog and click the Add button.At this point, scroll to the bottom of the screen and Save Changes. Then, click the Create Search Index button. Wait while MongoDB creates your search index --- it usually takes a few seconds to be active.
Once done, we should have two search indexes, as shown below.
With this done, let's update our backend API as below:
Open the
backend/index.js
file, and update it with the below code:1 //Country autocomplete endpoint goes here 2 app.get("/country/autocomplete/:param", async (req, res) => { 3 let results = await itemCollection.aggregate( 4 [ 5 { 6 '$search': { 7 'index': 'country_autocomplete', 8 'autocomplete': { 9 'query': req.params.param, 10 'path': 'address.country', 11 }, 12 'highlight': { 13 'path': [ 'address.country'] 14 } 15 } 16 }, { 17 '$limit': 1 18 }, { 19 '$project': { 20 'address.country': 1, 21 'highlights': { 22 '$meta': 'searchHighlights' 23 } 24 } 25 } 26 ]).toArray(); 27 28 res.send(results).status(200); 29 });
The above endpoint will return a suggestion of countries as the user types in the search box. In a three-stage aggregation pipeline, the first stage in the pipeline uses the
$search
operator to perform an autocomplete search on the address.country
field of the documents in the country_autocomplete
index. The query parameter is set to the user input provided in the URL parameter, and the highlight
parameter is used to return the matching text with highlighting.The second stage in the pipeline limits the number of results returned to one.
The third stage in the pipeline uses the
$project
operator to include only the address.country
field and the search highlights in the outputLet's also update the front end as below. From our project folder, open the
mdbsearch/components/Header/index.js
file. Find the handeChange
function, and let's update it with the below code.1 //Autocomplete function goes here 2 const handleChange = async (e) => { 3 setCountry(e.target.value); 4 if(e.target.value.length > 1){ 5 await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}country/autocomplete/${e.target.value}`) 6 .then((response) => response.json()) 7 .then(async (res) => { 8 setsug_countries(res) 9 }) 10 } 11 else{ 12 setsug_countries([]) 13 } 14 }
The above function will make a HTTP request to the
country/autocomplete
and save the response in a variable.With our code updated accordingly, let's explore our application. Everything should be fine now. We should be able to search homes by their country, and we should get suggestions as we type into the search box.
Voila! We now have fully functional text search for a home rental website. This will improve the user experience on the website.
To have a great user experience on a website, you'll agree with me that it's crucial to make it easy for your users to search for what they are looking for. In this guide, I showed you how I created a text search for a home rental website with MongoDB Atlas Search. This search will give users the ability to search for homes by their country.
MongoDB Atlas Search is a full-text search engine that enables developers to build rich search functionality into their applications, allowing users to search through large volumes of data quickly and easily. Atlas Search also supports a wide range of search options, including fuzzy matching, partial word matching, and wildcard searches. Check out more on MongoDB Atlas Search from the official documentation.
Questions? Comments? Let's continue the conversation! Head over to the MongoDB Developer Community --- we'd love to hear from you.