# Adding Autocomplete To Your NextJS Applications With Atlas Search

## Introduction

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. 

## Explanation of what we will be building

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.

## Overview of the technologies and tools that will be used

If you'd like to follow along, here's what I'll be using.

### Frontend framework

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.

### Backend framework

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.

### Database service provider

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

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.

## Pre-requisites

The full source of this application can be found on Github.

## Project setup

Let's get to work!

### Setting up our project

To start with, let's clone the repository that contains the starting source code on Github.

```bash
git clone https://github.com/mongodb-developer/search-nextjs/
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.

### Setting up a MongoDB account

To set up our MongoDB environment, we will need to follow the below instructions from the MongoDB official documentation.

-   Sign Up for a Free MongoDB Account

-   Create a Cluster

-   Add a Database User

-   Configure a Network Connection

-   Load Sample Data

-   Get Connection String

Your connection string should look like this: mongodb+srv://user:

### Identify a database to work with

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.

## Start the Node.js backend API

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.

```bash
cd backend
npm install
touch .env
```

Update .env as below

```bash
PORT=5050
MONGODB_URI=
```

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

```bash
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. 

## Start the NextJs frontend application

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.

```bash
cd ../mdbsearch
npm install
touch .env
```

Create a .env file, and update as below:

```bash
NEXT_PUBLIC_BASE_URL=http://localhost:5050/
```

Start the application by running the below command.

```bash
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.

## Implementing text search in our application with MongoDB Altas Search

To be able to search through data in our collection, we need to follow the below steps:

### Create a search index

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.

You can find detailed information on how to create a search index in the documentation.

## Testing our search 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.

## Consume search index in our backend application

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.

```javascript
// Search endpoint goes here
app.get("/search/:search", async (req, res) => {
  const queries = JSON.parse(req.params.search)
 // Aggregation pipeline goes here
 });
```

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.

```javascript
// Start building the search aggregation stage
let searcher_aggregate = {
    "$search": {
      "index": 'search_home',
      "compound": {
        "must": 
          // get home where queries.category is property_type
        { "text": {
          "query": queries.category,
          "path": 'property_type',
          "fuzzy": {}
        }},

// get home where queries.country is address.country
        {"text": {
          "query": queries.country,
          "path": 'address.country',
          "fuzzy": {}
        }}
      ]}
    }
  };
```

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.

```javascript
app.get("/search/:search", async (req, res) => {
 const queries = JSON.parse(req.params.search)
  // Start building the search aggregation stage
  let searcher_aggregate = { ...  };
  // A projection will help us return only the required fields
  let projection = {
          '$project': {
            'accommodates': 1,
            'price': 1,
            'property_type': 1,
            'name': 1,
            'description': 1,
            'host': 1,
            'address': 1,
            'images': 1,
            "review_scores": 1
          }
        };
 });
```

Finally, we can use the `aggregate` method to run this aggregation pipeline, and return the first 50 results to the front end.

```javascript
app.get("/search/:search", async (req, res) => {
  const queries = JSON.parse(req.params.search)
   // Start building the search aggregation stage
  let searcher_aggregate = { ... };
  // A projection will help us return only the required fields
  let projection = { ... };
  // We can now execute the aggregation pipeline, and return the first 50 elements
  let results = await itemCollection.aggregate([ searcher_aggregate, projection ]).limit(50).toArray();
  res.send(results).status(200);
 });
```

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.

The full source of this endpoint can be located on [Github .

## Implement search feature in our frontend application

From our project folder, open the `mdbsearch/components/Header/index.js` file.Find the `searchNow`function and update it with the below code.

```javascript
//Search function goes here
    const searchNow = async (e) => {
        setshow(false)
        let search_params = JSON.stringify({
            country: country,
            category: `${activeCategory}`
        })
        setLoading(true)
        await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}search/${search_params}`)
            .then((response) => response.json())
            .then(async (res) => {
                updateCategory(activeCategory, res)
                router.query = { country, category: activeCategory };
                setcountryValue(country);
                router.push(router);
            })
            .catch((err) => console.log(err))
            .finally(() => setLoading(false))
    }

```

NextFind the `handleChange`function, let update it with the below code 

```javascript
  const handleChange = async (e) => {
  //Autocomplete function goes here
 setCountry(e.target.value);
}
```

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.

## Add autocomplete to search box

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.

## Implement autocomplete API in our backend application

With this done, let's update our backend API as below:

Open the `backend/index.js` file, and update it with the below code:

```javascript
//Country autocomplete endpoint goes here
app.get("/country/autocomplete/:param", async (req, res) => {
  let  results = await itemCollection.aggregate(
      
        {
          '$search': {
            'index': 'country_autocomplete',
            'autocomplete': {
              'query': req.params.param,
              'path': 'address.country',
            },
            'highlight': {
              'path': [ 'address.country']
            }
          }
        }, {
          '$limit': 1
        }, {
          '$project': {
            'address.country': 1,
            'highlights': {
              '$meta': 'searchHighlights'
            }
          }
        }
      ]).toArray();

  res.send(results).status(200);
 });
```

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 output

## Implement autocomplete in our frontend application

Let'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.

```javascript
//Autocomplete function goes here
    const handleChange = async (e) => {
            setCountry(e.target.value);
            if(e.target.value.length > 1){
                await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}country/autocomplete/${e.target.value}`)
                .then((response) => response.json())
                .then(async (res) => {
                    setsug_countries(res)
                })
            }
            else{
                setsug_countries([])
            }
    }
```

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.

![showing text autocomplete.

Voila! We now have fully functional text search for a home rental website. This will improve the user experience on the website.

## Summary

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.