# Building a Restaurant Locator Using Atlas, Neurelo, and AWS Lambda

Ready to build a robust and efficient application that can quickly process real-time data, is capable of adapting to changing environments, and is fully customizable with seamless integration? 

The developer dream trifecta of MongoDB Atlas, Neurelo, and AWS Lambda will propel your cloud-based applications in ways you didn’t know were possible! With this lethal combination, you can build a huge variety of applications, like the restaurant locator we will build in this tutorial. 

This combination of platforms can help developers build scalable, cost-efficient, and performant serverless functions. Some huge benefits are that the Lambda functions used still remain stateless — data operations are now stateless API calls and there are no stateful connections opened with every Lambda invocation when Neurelo is incorporated in the application. We also are enabling higher performance and lower costs as no execution (and billing) time is spent setting up or tearing down established connections. This also enables significantly higher concurrency of Lambda invocations, as we can leverage built-in connection pooling through Neurelo which allows for you to open fewer connections on your MongoDB instance. 

We will be going over how to properly set up the integration infrastructure to ensure you’re set up for success, and then we will dive into actually building our application. At the end, we will have a restaurant locator that we can use to search for restaurants that fit our desired criteria. Let’s get started! 

## Pre-reqs

 - MongoDB Atlas account
 - Neurelo account
 - AWS account; Lambda access is necessary 

## Setting up our MongoDB cluster 

Our first step is to spin up a free MongoDB cluster and download the sample dataset. For help on how to do this, please refer to our tutorial. 

For this tutorial, we will be using the `sample_restaurants` collection that is located inside the sample dataset. Please ensure you have included the correct IP address access for this tutorial, along with a secure username and password as you will need these throughout. 

Once your cluster is up and running, we can start setting up our Neurelo project.

## Setting up our Neurelo project

Once we have our MongoDB cluster created, we need to create a project in Neurelo. For help on this step, please refer to our first tutorial in this series, Neurelo and MongoDB: Getting Started and Fun Extras. 

Save your API key someplace safe. Otherwise, you will need to create a new key if it gets lost. Additionally, please ensure your Neurelo project is connected to your MongoDB cluster. For help on grabbing a MongoDB connection string, we have directions to guide you through it. Now, we can move on to setting up our AWS Lambda function. 

##  Creating our AWS Lambda function

Log into your AWS account and access Lambda either through the search bar or in the “Services” section. Click on the orange “Create function” button and make sure to press the “Author from scratch” option on the screen that pops up. Select a name for your function — we are using “ConnectTest” to keep things simple — and then, choose “Python 3.12” for your runtime, since this is a Python tutorial! Your Lambda function should look like this prior to hitting “Create function.”

Once you’re taken to the “Function overview” page, we can start writing our code to perfectly integrate MongoDB Atlas, Neurelo, and AWS Lambda. Let’s dive into it.

## Part 1: The integration

Luckily, we don’t need to import any requirements for this Lambda function tutorial and we can write our code directly into the function we just created. 

The first step is to import the packages `urllib3` and `json` with the line:

```
import urllib3, json
```
These two packages hold everything we need to deal with our connection strings and make it so we don’t need to write our code in a separate IDE. 

Once we have our imports in, we can configure our API key to our Neurelo environment. We are using a placeholder `API_KEY`, and for ease in this tutorial, you can put your key directly in. But it’s not good practice to ever hardcode your keys in code, and in a production environment, it should never be done. 

```
# Put in your API Key to connect to your Neurelo environment
NEURELO_API_KEY = ‘API_KEY’
```
Once you’ve set up your API key connection, we can set up our headers for the REST API call. For this, we can take the auto-generated `lambda_function` function and edit it to better suit our needs:

```
def lambda_handler(event, context):
   
    # Setup the headers
    headers = {
        'X-API-KEY': NEURELO_API_KEY
    }
    
    # Creating a PoolManager instance for sending HTTP requests
    http = urllib3.PoolManager()
```
Here, we are creating a dictionary named `headers` to set the value of our API key. This step is necessary so Neurelo can authenticate our API request and we can return our necessary documents. We are then utilizing the `PoolManager` class to manage our server connections. This is an efficient way to ensure we are reusing connections with Lambda instead of creating a new connection with each individual call. For this tutorial, we are only using one connection, but if you have a more complex Lambda or a project with the need for multiple connections, you will be able to see the magic of the `PoolManager` class a bit more.  

Now, we are ready to set up our first API call! Please recall that in this first step, we are connecting to our “restaurants” collection within our `sample_restaurants` database and we are returning our necessary documents. 

We have decided that we want to retrieve a list of restaurants from this collection that fit specific criteria: These restaurants are located in the borough of Brooklyn, New York, and serve American cuisine. Prior to writing the code below, we suggest you take a second to look through the sample database to view the fields inside our documents. 

So now that we’ve defined the query parameters we are interested in, let’s translate it into a query request. We are going to be using three parameters for our query: “filter,” “take,” and “select.” These are the same parameter keys from our first article in this series, so please refer back to it if you need help. We are using the “filter” parameter to ensure we are receiving restaurants that fit our criteria of being in Brooklyn and that are American, the “take” parameter is so we only return five documents instead of thousands (our collection has over 25,000 documents!), and the “select” parameter is so that only our specific fields are being returned in our output. 

Our query request will look like this:
```
# Define the query parameters
    params1 = {
        'filter': '{"AND": {"borough": {"equals": "Brooklyn"}, "cuisine": {"equals": "American"}}}',
        'take': '5',
        'select': '{"id": false, "name": true, "borough": true, "cuisine": true}',
    }
```
Don’t forget to send a GET request with our necessary parameters, and set up some print statements so we can see if our request was successful. Once completed, the whole code block for our Part 1 should look something like this: 
```
import urllib3, json

# Configure the API Key for our Neurelo environment
NEURELO_API_KEY = 'API_KEY'

def lambda_handler(event, context):
   
    # Setup the headers
    headers = {
        'X-API-KEY': NEURELO_API_KEY
    }
    
    # Creating a PoolManager instance for sending HTTP requests
    http = urllib3.PoolManager()

    # Choose the "restaurants" collection from our Neurelo environment connected to 'sample_restaurants'
    api1 = 'https://us-east-2.aws.neurelo.com/rest/restaurants'
  
    # Define the query parameters
    params1 = {
        'filter': '{"AND": {"borough": {"equals": "Brooklyn"}, "cuisine": {"equals": "American"}}}',
        'take': '5',
        'select': '{"id": false, "name": true, "borough": true, "cuisine": true}',
    }
   
    # Send a GET request with URL parameters
    response = http.request("GET", api1, headers=headers, fields=params1)
  
    # Print results if the request was successful
    if response.status == 200:
        # Print the JSON content of the response
        print ('Restaurants Endpoint: ' + json.dumps(json.loads(response.data), indent=4))
```
And our output will look like this:

Congratulations! As you can see, we have successfully returned five American cuisine restaurants located in Brooklyn, and we have successfully integrated our MongoDB cluster with our Neurelo project and have used AWS Lambda to access our data. 

Now that we’ve set everything up, let’s move on to the second part of our tutorial, where we will filter our results with a custom API endpoint for the best restaurants possible. 

## Part 2: Filtering our results further with a custom API endpoint

Before we can call our custom endpoint to filter for our desired results, we need to create one. While Neurelo has a large list of auto-generated endpoints available for your project, sometimes we need an endpoint that we can customize with a complex query to return information that is nuanced. From the sample database in our cluster, we can see that there is a `grades` field where the grade and score received by each restaurant exist. 

So, what if we want to return documents based on their scores? Let’s say we want to expand our search and find restaurants that are really good restaurants. 

Head over to Neurelo and access the “Definitions” tab on the left-hand side of the screen. Go to the “Custom Queries” tab and create a complex query named “getGoodRestaurants.” For more help on this section, please refer to the first article in this series for a more detailed explanation. 

We want to filter restaurants where the most recent grades are either “A” or “B,” and the latest grade score is greater than 10. Then, we want to aggregate the restaurants by cuisine and borough and list the restaurant name, so we can know where to go!

Our custom query will look like this:

```
{
 "aggregate": "restaurants",
 "pipeline": 
   {
     "$match": {
       "borough": "Brooklyn",
       "cuisine": "American",
       "grades.0.grade": {
         "$in": [
           "A",
           "B"
         ]
       },
       "grades.1.grade": {
         "$in": [
           "A",
           "B"
         ]
       },
       "grades.0.score": {
         "$gt": 10
       }
     }
   },
   {
     "$limit": 5
   },
   {
     "$group": {
       "_id": {
         "cuisine": "$cuisine",
         "borough": "$borough"
       },
       "restaurants_info": {
         "$push": {
           "name": "$name"         
}
       }
     }
   }
 ],
 "cursor": {}
}

```
Great! Now that we have our custom query in place, hit the “Commit” button at the top of the screen, add a commit message, and make sure that the “Deploy to environment” option is selected. This is a crucial step that will ensure that we are committing our custom query into the definitions repo for the project and deploying the changes to our environment. 

Now, we can head back to Lambda and incorporate our second endpoint to return restaurants that have high scores serving our desired food in our desired location. 

Add this code to the bottom of the previous code we had written. 

```
# Choose the custom-query endpoint from our Neurelo environment connected to 'sample_restaurants'
    api2 = 'https://us-east-2.aws.neurelo.com/custom/getGoodRestaurants'

    # Send a GET request with URL parameters
    response = http.request("GET", api2, headers=headers)
    
    if response.status == 200:
        # Print the JSON content of the response
        print ('Custom Query Endpoint: ' + json.dumps(json.loads(response.data), indent=4))
```
Here, we are choosing our custom endpoint, `getGoodRestaurants`, and then sending a GET request to acquire the necessary information. 

Please deploy the changes in Lambda and hit the “Test” button. 

Your output will look like this:
![[Fig 4: custom complex query endpoint output in Lambda]

As you can see from the results above, we have received a sample size of five American cuisine, Brooklyn borough restaurants that meet our criteria and are considered good restaurants! 

## Conclusion

In this tutorial, we have covered how to properly integrate a MongoDB Atlas cluster with our Neurelo project and return our desired results by using AWS Lambda. We have shown the full process of utilizing our Neurelo project automated API endpoints and even how to use unique and fully customizable endpoints as well! 

For more help with using MongoDB Atlas, Neurelo, and AWS Lambda, please visit the hyperlinked documentation. 

> This tutorial is the second in our series. Please check out the first tutorial: Neurelo and MongoDB: Getting Started and Fun Extras.