# Scaling for Demand: Deploying Python Applications Using MongoDB Atlas on Azure App Service

Managing large amounts of data locally can prove to be a challenge, especially as the amount of saved data grows. Fortunately, there is an efficient solution available. By utilizing the features of Flask, MongoDB Atlas, and Azure App Service, you can build and host powerful web applications that are capable of storing and managing tons of data in a centralized, secure, and scalable manner. Say goodbye to unreliable local files and hello to a scalable solution. 

This in-depth tutorial will teach you how to build a functional CRUD (Create, Read, Update, and Delete) Flask application that connects to a MongoDB Atlas database, and is hosted on Azure App Service. Using Azure App Service and MongoDB together can be a great way to build and host web applications. Azure App Service makes it easy to build and deploy web apps, while MongoDB is great for storing and querying large amounts of data. With this combination, you can focus on building your application and let Azure take care of the underlying infrastructure and scaling. 

This tutorial is aimed at beginners, but feel free to skip through this article and focus on the aspects necessary to your project. 

We are going to be making a virtual bookshelf filled with some of my favorite books. Within this bookshelf, we will have the power to add a new book, view all the books in our bookshelf, exchange a book for another one of my favorites, or remove a book we would like to read. At the end of our tutorial, our bookshelf will be hosted so anyone with our website link can enjoy our book list too.

### Requirements
Before we begin, there are a handful of prerequisites we need:

* MongoDB Atlas account.
* Microsoft Azure App Services subscription. 
* Postman Desktop (or another way to test our functions).
* Python 3.9+.  
* GitHub Repository.

### Setting up a MongoDB Atlas cluster
Within MongoDB Atlas, we need to create a free cluster. Follow the instructions in our MongoDB Atlas Tutorial. Once your cluster has provisioned, create a database and collection within Atlas. Let’s name our database “bookshelf” and our collection “books.” Click on “Insert Document” and add in a book so that we have some data to start with. Your setup should look like this:

Now that we have our bookshelf set up, we are ready to connect to it and utilize our CRUD operations. Before we get started, let’s focus on *how* to properly connect.

## Cluster security access
Now that we have our cluster provisioned and ready to use, we need to make sure we have proper database access. Through Atlas, we can do this by heading to the “Security” section on the left-hand side of the screen. Ensure that under “Database Access,” you have enabled a user with at least “Read and Write'' access. Under “Network Access,” ensure you’ve added in any and all IP addresses that you’re planning on accessing your database from. An easy way to do this is to set your IP address access to “0.0.0.0/0.” This allows you to access your cluster from any IP address. Atlas provides additional optional security features through Network Peering and Private Connections, using all the major cloud providers. Azure Private Link is part of this additional security feature, or if you’ve provisioned an M10 or above cluster, the use of Azure Virtual Private Connection. 

## Setting up a Python virtual environment
Before we open up our Visual Studio Code, use your terminal to create a directory for where your bookshelf project will live.

Once we have our directory made, open it up in VSCode and access the terminal inside of VSCode. We are going to set up our Python virtual environment. We do this so all our files have a fun spot to live, where nothing else already downloaded can bother them. 

Set up your environment with:
```
python3 -m venv venv
```
Activate your environment with:
```
source venv/bin/activate
```
You’ll know you’re in your virtual environment when you see the little (venv) at the beginning of your hostname in your command line. 

Once we are in our virtual environment, we are ready to set up our project requirements. A ‘requirements.txt’ file is used to specify the dependencies (various packages and their versions) required by the project to run. It helps ensure the correct versions of packages are installed when deploying the project to a new environment. This makes it much easier to reproduce the development environment and prevents any compatibility issues that may arise when using different versions of dependencies. 

## Setting up our ‘requirements.txt’ file
Our ‘requirements.txt’ file will consist of four various dependencies this project requires. The first is Flask. Flask is a web micro-framework for Python. It provides the basic tools for building web apps, such as routing and request handling. Flask allows for easy integration with other libraries and frameworks and allows for flexibility and customizability. If you’ve never worked with Flask before, do not worry. By the end of this tutorial, you will have a clear understanding of how useful Flask can be.

The second dependency we have is PyMongo. PyMongo is a Python library for working with MongoDB. It provides a convenient way to interact with MongoDB databases and collections. We will be using it to connect to our database.

The third dependency we have is Python-dotenv. This is a tool used to store and access important information, like passwords and secret keys, in a safe and secure manner. Instead of hard-coding this information, Python-dotenv allows us to keep this information in an environment variable in a separate file that isn’t shared with anyone else. Later in this tutorial, we will go into more detail on how to properly set up environment variables in our project. 

The last dependency we have in our file is Black. Black is a code formatter for Python and it enforces a consistent coding style, making it easier for developers to read and maintain the code. By using a common code style, it can improve readability and maintainability.

Include these four dependencies in your ‘requirements.txt’ file.

```
Flask==2.2.2
pymongo==4.3.3
python-dotenv==0.21.1
black==22.12.0
```
This way, we can install all our dependencies in one step:
```
pip install -r requirements.txt
```

***Troubleshooting***: After successfully installing PyMongo, a line in your terminal saying `dnspython has been installed` will likely pop up. It is worth noting that without `dnspython` properly downloaded, our next package `dotenv` won’t work. If, when attempting to run our script later, you are getting `ModuleNotFoundError: No module named dotenv`, include `dnspython==2.2.1` in your ‘requirements.txt’ file and rerun the command from above.

## Setting up our ‘app.py’ file
Our ‘app.py’ file is the main file where our code for our bookshelf project will live. Create a new file within our “azuredemo” directory and name it ‘app.py’. It is time for us to include our imports:
```
import bson 
import os
from dotenv import load_dotenv 
from flask import Flask, render_template, request
from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.database import Database
```
Here we have our environment variable imports, our Flask imports, our PyMongo imports, and the BSON import we need in order to work with binary JSON data. 

Once we have our imports set up, we are ready to connect to our MongoDB Atlas cluster and implement our CRUD functions, but first let’s test and make sure Flask is properly installed. 

Run this very simple Flask app: 
```
app: Flask = Flask(__name__)
# our initial form page 
@app.route(‘/’) 
def index():
return “Hi!”
```
Here, we continue on to creating a new Flask application object, which we named “app” and give it the name of our current file. We then create a new route for the application. This tells the server which URL to listen for and which function to run when that URL is requested. In this specific example, the route is the homepage, and the function that runs returns the string “Hi!”.

Run your flask app using:
```
flask run
```

This opens up port 5000, which is Flask’s default port, but you can always switch the port you’re using by running the command: 
```
flask run -p port number]
```

When we access [http://127.0.0.1:5000, we see: 

So, our incredibly simple Flask app works! Amazing. Let’s now connect it to our database. 

## Connecting our Flask app to MongoDB
As mentioned above, we are going to be using a database environment variable to connect our database. In order to do this, we need to set up an .env file. Add this file in the same directory we’ve been working with and include your MongoDB connection string. Your connection string is a URL-like string that is used to connect to a MongoDB server. It includes all the necessary details to connect to your specific cluster. This is how your setup should look:

Change out the `username` and `password` for your own. Make sure you have set the proper Network Access points from the paragraph above. 

We want to use environment variables so we can keep them separate from our code. This way, there is privacy since the `CONNECTION_STRING` contains sensitive information. It is crucial for security and maintainability purposes.

Once you have your imports in, we need to add a couple lines of code above our Flask instantiation so we can connect to our .env file holding our `CONNECTION_STRING`, and connect to our Atlas database. At this point, your app.py should look like this:

```
import bson 
import os
from dotenv import load_dotenv 
from flask import Flask, render_template, request
from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.database import Database
# access your MongoDB Atlas cluster
load_dotenv()
connection_string: str = os.environ.get(“CONNECTION_STRING”)
mongo_client: MongoClient = MongoClient(connection_string)

# add in your database and collection from Atlas 
database: Database = mongo_client.get_database(“bookshelf”)
collection: Collection = database.get_collection(“books”)
# instantiating new object with “name”
app: Flask = Flask(__name__)

# our initial form page
@app.route(‘/’)
def index():
return “Hi!”
```

Let’s test `app.py` and ensure our connection to our cluster is properly in place. 
Add in these two lines after your `collection = database“books”]` line and before your `#instantiating new object with name` line to check and make sure your Flask application is really connected to your database:

```
book = {“title”: “The Great Gatsby”, “author”: “F. Scott Fitzgerald”, “year”: 1925}
collection.insert_one(book)
```

Run your application, access Atlas, and you should see the additional copy of “The Great Gatsby” added. 

![screenshot of our “books” collection showing both copies of “The Great Gatsby”

Amazing! We have successfully connected our Flask application to MongoDB. Let’s start setting up our CRUD (Create, Read, Update, Delete) functions. 

Feel free to delete those two added lines of code and manually remove both the Gatsby documents from Atlas. This was for testing purposes!

## Creating CRUD functions
Right now, we have hard-coded in our “Hi!” on the screen. Instead, it’s easier to render a template for our homepage. To do this, create a new folder called “templates” in your directory. Inside of this folder, create a file called: `index.html`. Here is where all the HTML and CSS for our homepage will go. This is highly customizable and not the focus of the tutorial, so please access this code from my Github (or make your own!).

Once our `index.html` file is complete, let’s link it to our `app.py` file so we can read everything correctly. This is where the addition of the `render_template` import comes in. Link your `index.html` file in your initial form page function like so:
```
# our initial form page
@app.route(‘/’)
def index():
return render_template(“index.html”)
```

When you run it, this should be your new homepage when accessing http://127.0.0.1:5000/:

We are ready to move on to our CRUD functions.

#### Create and read functions
We are combining our two Create and Read functions. This will allow us to add in a new book to our bookshelf, and be able to see all the books we have in our bookshelf depending on which request method we choose.

```
# CREATE and READ 
@app.route('/books', methods="GET", "POST"])
def books():
    if request.method == 'POST':
        # CREATE
        book: str = request.json['book']
        pages: str = request.json['pages']

        # insert new book into books collection in MongoDB
        collection.insert_one({"book": book, "pages": pages})

        return f"CREATE: Your book {book} ({pages} pages) has been added to your bookshelf.\n "

    elif request.method == 'GET':
        # READ
        bookshelf = list(collection.find())
        novels = []

        for titles in bookshelf:
            book = titles['book']
            pages = titles['pages']
            shelf = {'book': book, 'pages': pages}
            novels.insert(0,shelf)

        return novels
```

This function is connected to our ‘/books’ route and depending on which request method we send, we can either add in a new book, or see all the books we have already in our database. We are not going to be validating any of the data in this example because it is out of scope, but please use Postman, cURL, or a similar tool to verify the function is properly implemented. For this function, I inserted:

```
{
  “book”: “The Odyssey”,
  “pages”: 384
}
```
If we head over to our Atlas portal, refresh, and check on our “bookshelf” database and “books” collection, this is what we will see:

![screenshot of our “books” collection showing “The Odyssey”

Let’s insert one more book of our choosing just to add some more data to our database. I’m going to add in “*The Perks of Being a Wallflower*.”

Amazing! Read the database collection back and you should see both novels.  

Let’s move onto our UPDATE function.

#### Update
For this function, we want to exchange a current book in our bookshelf with a different book. 

```
# UPDATE
@app.route("/books/", methods = 'PUT'])
def update_book(book_id: str):
    new_book: str = request.json['book']
    new_pages: str = request.json['pages']
    collection.update_one({"_id": bson.ObjectId(book_id)}, {"$set": {"book": new_book, "pages": new_pages}})

    return f"UPDATE: Your book has been updated to: {new_book} ({new_pages} pages).\n"

```

This function allows us to exchange a book we currently have in our database with a new book. The exchange takes place via the book ID. To do so, access Atlas and copy in the ID you want to use and include this at the end of the URL. For this, I want to switch “The Odyssey” with “The Stranger”. Please use your testing tool to communicate to the update endpoint and view the results in Atlas. 

Once you hit send and refresh your Atlas database, you’ll see: 
![screenshot of our “books” collection with “The Stranger” and “The Perks of Being a Wallflower”

“The Odyssey” has been exchanged with “The Stranger”! 

Now, let’s move onto our last function: the DELETE function. 

#### Delete
```
# DELETE
@app.route("/books/", methods = 'DELETE'])
def remove_book(book_id: str):
    collection.delete_one({"_id": bson.ObjectId(book_id)})

    return f"DELETE: Your book (id = {book_id}) has been removed from your bookshelf.\n"
```
This function allows us to remove a specific book from our bookshelf. Similarly to the UPDATE function, we need to specify which book we want to delete through the URL route using the novels ID. Let’s remove our latest book from the bookshelf to read, “The Stranger”. 

Communicate with the delete endpoint and execute the function. 

In Atlas our results are shown:

![screenshot of the “books” collection showing “The Perks of Being a Wallflower”

“The Stranger” has been removed!

Congratulations, you have successfully created a Flask application that can utilize CRUD functionalities, while using MongoDB Atlas as your database. That’s huge. But…no one else can use your bookshelf! It’s only hosted locally. Microsoft Azure App Service can help us out with this. Let’s host our Flask app on App Service. 

## Host your application on Microsoft Azure App Service
We are using Visual Studio Code for this demo, so make sure you have installed the Azure extension and you have signed into your subscription. There are other ways to work with Azure App Service, and to use Visual Studio Code is a personal preference. 

If you’re properly logged in, you’ll see your Azure subscription on the left-hand side. 

Click the (+) sign next to Resources:

Click on “Create App Service Web App”:

Enter a new name. This will serve as your website URL, so make sure it’s not too hectic:

Select your runtime stack. Mine is Python 3.9:

Select your pricing tier. The free tier will work for this demo. 

In the Azure Activity Log, you will see the web app being created. 

You will be asked to deploy your web app, and then choose the folder you want to deploy:

It will start deploying, as you’ll see through the “Output Window” in the Azure App Service Log. 

Once it’s done, you’ll see a button that says “Browse Website.” Click on it. 

As you can see, our application is now hosted at a different location! It now has its own URL.

Let’s make sure we can still utilize our CRUD operations with our new URL. Test again for each function.

At each step, if we refresh our MongoDB Atlas database, we will see these changes take place there as well. Great job!

## Conclusion
Congratulations! We have successfully created a Flask application from scratch, connected it to our MongoDB database, and hosted it on Azure App Service. These skills will continue to come in handy and I hope you enjoyed the journey. Separately, Azure App Service and MongoDB host a variety of benefits. Together, they are unstoppable! Combined, they provide a powerful platform for building and scaling web applications that can handle large amounts of data. Azure App Service makes it easy to deploy and scale web apps, while MongoDB provides a flexible and scalable data storage solution.  

Get information on MongoDB Atlas, Azure App Service, and Flask.

If you liked this tutorial and would like to dive even further into MongoDB Atlas and the functionalities available, please view my YouTube video.