My first application using flask, Mongodb and Github Actions
Introduction
What is the purpose of MongoDB?
MongoDB is a popular NoSQL database that stores data in a flexible, JSON-like format called BSON (Binary JSON). Unlike traditional relational databases, MongoDB is designed to handle large amounts of unstructured or semi-structured data. It allows developers to:
- Scale Horizontally: MongoDB can handle vast amounts of data and traffic by distributing data across many servers, making it ideal for large applications.
- Flexible Schema: It does not require a fixed schema, meaning that each document in a collection can have a different structure. This flexibility is useful for handling dynamic data.
- Speed and Performance: MongoDB is optimized for fast read and write operations, which is especially beneficial for real-time applications.
In the simple store application, MongoDB is well-suited to store product information, where the data structure might evolve over time.
What is the purpose of Flask
Flask is a lightweight web framework for Python that allows developers to build web applications. It is classified as a micro-framework because it provides only the essential tools, allowing developers to add additional functionality as needed.
When building the simple store application, Flask provides the foundation for creating routes, handling requests, and rendering views, while MongoDB stores the back-end data.
The Role of CI/CD Pipelines
Modern software development thrives on speed and stability. Continuous Integration and Continuous Deployment (CI/CD) pipelines streamline the development process by automating testing, integration, and deployment. With GitHub Actions, developers can:
- Ensure Code Quality: Automated tests catch bugs early, reducing manual intervention.
- Accelerate Deployment: Push changes to production with confidence, knowing that all tests have passed.
In this project, we’ll use a GitHub Actions CI/CD pipeline to ensure that our Flask application and its integration with the Gemini API works as expected. This pipeline will automate testing the API’s functionality.
In the sections to follow, we’ll dive deeper into building the Flask application, integrating it with the MongoDB, and setting up the CI/CD pipeline to test and deploy the project.
The Project Setup
Setting up the MongoDB Atlas
- Login to MongoDB Atlas or Create an account
- Create a new Cluster & select M2 for the initial free tier
- Once the database has been created click on connect to get the connection link.
Setting and activating the virtual environment
- Creating the virtual environment > Make sure virtualenv is installed
$ pip install virtualenv
$ python -m venv <environment_name>
$ source <environment_name>/bin/activate
Note: You can deactivate the virtualenv by typing deactivate
Setting up the project
-
Install the MongoDB Python Driver
- On your local machine, make sure you have Python installed.
- Install the
pymongolibrary by running:
pip install pymongo -
Test the Connection
- In your Python script, use the following code to test the connection:
from pymongo import MongoClient # Replace with your connection string client = MongoClient("<Connection String>") db = client.test # Access the test database collection = db.test_collection # Access the test collection # Insert a test document collection.insert_one({"name": "Test Document", "value": 1}) print("Connected to MongoDB Atlas and inserted a document!")
- Replace
<Connection String>with the connection string you copied earlier. - Run the script to ensure that the connection to MongoDB Atlas is working.
- The Routes Create the necessary routes
# Routing to / to render home.html
@app.route("/")
def home():
return render_template('home.html')
# Routing to /products to render Products and display the products from the mongodb shop_db database
@app.route("/products")
def products():
products = products_collection.find()
return render_template("products.html", products = products)
-
The Templates
Create the templates that has to be rendered.
- The Base Template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Store</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='css/styles.css')}}"
</head>
<body>
<nav data-bs-theme="dark" class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Store</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{url_for('home')}}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{{url_for('products')}}">Products</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
{% block pageContent %} {% endblock %}
</div>
<footer>
Store
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>
- The Home Template:
{% extends "base.html" %}
{% block pageContent %}
<div class="homepage_body">
<div class="card card-homepage">
<img src="static/images/sales.jpg" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">STORE</h5>
<p class="card-text">View our cheapest products.</p>
<a href="{{url_for('products')}}" class="btn btn-primary button-link">View Our Products</a>
</div>
</div>
</div>
{% endblock %}
- The Products Template:
{% extends "base.html" %}
{% block pageContent %}
<h1 class="title"> Our Products </h1>
<br/>
<div class="table_format">
<table class="table table-dark table-hover">
<thead>
<th>Product Image</th>
<th>Product Name</th>
<th>Product Tag</th>
<th>Product Price</th>
</thead>
<tbody>
{% for product in products %}
<tr>
<td><img src="static/{{product.image_path}}" alt="Picture of {{product.name}}" /></td>
<td>{{product.name}}</td>
<td>{{product.tag}}</td>
<td>$ {{product.price}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
Running the Application
Note: Make sure the virtualenv is activate
- Running the app.py in development mode:
python app.py
- Open localhost:
http://127.0.0.1:5000
Testing the Application
- Create a test directory
- Route testing:
import unittest
from app import app
class FlaskRouteTest(unittest.TestCase):
# Set up the Flask test client
def setUp(self):
self.app = app.test_client()
self.app.testing = True
# Test for invalid method (POST instead of GET)
def test_home_route_invalid_method(self):
# Send a POST request to the home route
response = self.app.post('/')
# Expect 405 Method Not Allowed when trying to send post request for '/'
self.assertEqual(response.status_code, 405)
# Test if the /products route works with GET
def test_products_route_get(self):
# Send a GET request to the /products route
response = self.app.get('/products')
# Expect 200 OK
self.assertEqual(response.status_code, 200)
# Check if the page contains the word "Our Products" - The title
self.assertIn(b"Our Products", response.data)
if __name__ == "__main__":
unittest.main()
- Testing the Database: Pinging the database:
import unittest
from pymongo import MongoClient
from app import MONGO_USERNAME, MONGO_PASSWORD
# Test 2: Database Read Operation -
# Write a unit test to check the correct connection of a MongoDB read operation.
class MongoDBConnectionTest(unittest.TestCase):
def test_mongo_connection(self):
# Constructing the MongoDB URI, getting the MONGO_USERNAME AND PASSWORD FROM THE APP
URI = "<MONGODB_URI>"
# Creating MongoClient, timeout in 5 seconds for testing
client = MongoClient(URI, serverSelectionTimeoutMS=5000)
try:
# Try to ping the database to verify the connection
client.admin.command('ping')
# If no exception is raised, connection is successful
self.assertTrue(True)
except Exception as e:
# If Error is thrown Fail with Error
self.fail(f"MongoDB connection failed: {e}")
finally:
# Ensure MongoClient is closed to avoid resource warnings
client.close()
if __name__ == "__main__":
unittest.main()
- Writing to the database:
import unittest
from pymongo import MongoClient
from app import MONGO_USERNAME, MONGO_PASSWORD
# Test 3: Database Write Operation
# Unit test for a MongoDB write operation
class MongoDBWriteOperationTest(unittest.TestCase):
def setUp(self):
# Constructing the MongoDB URI, getting the MONGO_USERNAME AND PASSWORD FROM THE APP
URI = "<MONGODB_URI>"
# Creating MongoClient, timeout in 5 seconds for testing
self.client = MongoClient(URI, serverSelectionTimeoutMS=5000)
# Setting DB and Collection
self.db = self.client.shop_db
self.collection = self.db.products
def test_insert_document(self):
# Creating a product to insert
product = {
"name": "House",
"tag": "Test",
"price": 999.99,
"image_path": "images/house.jpg"
}
# Insert the product into the database
result = self.collection.insert_one(product)
# Verify the insertion
# Query the database to check if the document is present
inserted_product = self.collection.find_one({"_id": result.inserted_id})
# Assertions to verify the document
self.assertIsNotNone(inserted_product, "The document was not inserted.");
self.assertEqual(inserted_product["name"], product["name"]);
self.assertEqual(inserted_product["tag"], product["tag"]);
self.assertEqual(inserted_product["price"], product["price"]);
self.assertEqual(inserted_product["image_path"], product["image_path"]);
# Cleanup: Remove the inserted document after the test
self.collection.delete_one({"_id": result.inserted_id})
# Clean up
def tearDown(self):
# Close the client connection
self.client.close()
if __name__ == "__main__":
unittest.main()
- Running the test: The following command will run all the test under the test directory
python -m unittest discover test
Integrating with Github Actions
- Create the following directory to include Github Action's configuration:
.github/workflows/main.yml
- Configuring the yml file:
on:
push:
branches:
- main # Trigger on push to the main branch
pull_request:
branches:
- main # Trigger on pull requests targeting the main branch
jobs:
test:
runs-on: ubuntu-latest # Use the latest Ubuntu runner
steps:
# Step 1: Checkout the repository code
- name: Checkout code
uses: actions/checkout@v3
# Step 2: Set up Python 3.8
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: '3.8'
# Step 3: Install dependencies from requirements.txt inside the 'flask_app' directory
- name: Install dependencies
run: |
cd flask_app
python -m pip install --upgrade pip
pip install -r requirements.txt
# Step 4: Set up MongoDB credentials as environment variables
- name: Set up MongoDB environment variables
run: |
echo "MONGO_USERNAME=${{ secrets.MONGO_USERNAME }}" >> $GITHUB_ENV
echo "MONGO_PASSWORD=${{ secrets.MONGO_PASSWORD }}" >> $GITHUB_ENV
# Step 5: Run the tests located in 'flask_app/tests'
- name: Run Tests in 'flask_app/tests'
run: |
cd flask_app
python -m unittest discover tests
- Testing the workflow:
- Once a change has been pushed, GitHub Action will trigger the automated testing:
Conclusion
Integrating GitHub Actions for Continuous Integration and Continuous Deployment (CI/CD) with MongoDB Atlas and Flask takes your development workflow to the next level by automating testing, building, and deployment processes. This setup allows you to easily deploy your Flask application to any environment while ensuring that your code is tested and validated every time a change is made.
By setting up MongoDB Atlas, you’ve taken care of the backend database, providing a scalable and secure cloud-based solution. With GitHub Actions, you can now automate your deployments, ensuring your code is always up-to-date and reliable across all stages of development, from testing to production.
This CI/CD pipeline reduces manual intervention, eliminates errors in deployments, and boosts efficiency by integrating the entire workflow from code commit to live production. Whether you're building a simple store application or a more complex platform, combining Flask, MongoDB Atlas, and GitHub Actions offers a streamlined, modern solution for both development and deployment.
References
How to Set Up a Virtual Environment in Python – And Why It's Useful by Stephen Sanwo: Setting up Virtualenv
MongoDB Atlas Documentation: MongoDB Atlas Documentation
Flask Documentation: Flask Documentation
MongoDB Atlas – Free Tier Overview: MongoDB Atlas Free Tier
PyMongo Documentation: PyMongo Documentation
GitHub Actions CI/CD Guide: GitHub Actions CI/CD Guide


Top comments (0)