Building a Python Note Application Using Flask and MongoDB

September 16, 2021

Python is a very easy-to-learn language due to its user-friendly syntax. Moreover, Python has many open-source libraries, and almost every use case of Python has an existing library for that.

Python has several web application frameworks such as Django and Flask. Flask is an open-source, lightweight, Python web application framework.

Flask is designed to make it easy to get a simple application up and running.

We will use MongoDB as the database. MongoDB is a cross-platform, document-oriented database platform. It uses JSON objects as its data tuples.

When working with a web application, you might not know the exact data format being sent. In such cases, a NoSQL database such as MongoDB would be a good solution for data handling and storage.

In this article, we will create a simple note application using Flask and MongoDB.

Prerequisites

To follow along with this article, the following basic information will be essential:

  • Basic knowledge of working with Python.
  • Python installed on your computer.
  • MongoDB installed on your computer.
  • Pip installed on your computer.

Setting up the Flask application

We will use virtualenv to set up the application. Virtualenv is a tool for creating standalone Python environments. It prevents global installation of dependencies that are only used in a single application.

Run the command below to install virtualenv:

pip install virtualenv

To test whether virtualenv is correctly installed, run the following command:

virtualenv --version

To start, run the following command in the project folder where you want the project to reside:

virtualenv venv

If the environment is not activated automatically by having the name of your project folder on the left side, then run the following command to activate it:

source venv/bin/activate

Once activated, we need two dependencies:

  • flask - It is essential for setting up the web resource.
  • PyMongo - This will form the infrastructure from MongoDB to our application.

To install the dependencies, use the command below:

pip install flask PyMongo

Setting up MongoDB

To begin setting up MongoDB in our application, we need to create an app.py file at the project root folder. This will be our main file.

In this app.py file, add the following block of code:

from flask import Flask
from flask_pymongo import PyMongo

app = Flask(__name__)
app.config["MONGO_URI"] = "mongodb://localhost:27017/flaskCrashCourse"
mongo = PyMongo(app)

if __name__ == "__main__":
    app.run(debug=True)

Here, we have done basic configuration for a basic Flask app. We;

  • Have imported the necessary packages.
  • Set up the main app variable.
  • Initialized an instance of PyMongo.
  • Started the app in debug mode. This means that each change we make to the app will be reloaded automatically.

To test this, run the command below:

python3 app.py

The command above will start the application. You can access it from port number 5000; i.e. http://localhost:5000. For now, you will get a Not Found message since we have not defined any route.

Let us get to that in the next step.

Setting up the routes

First, let us import the request module from flask:

from flask import request, Flask

We will have four routes:

  • Route for fetching notes.
  • Route for adding a note.
  • Route for editing a note.
  • Route for deleting a note.

Let us start by adding the route for fetching notes. This will be the home page.

Add the following in your app.py file:

@app.route('/')
def home():
    return "<p>The home page</p>"

Whenever one visits the home page (/), the decorator will execute the function below, which returns a paragraph.

Let us do the same for the add-note route, edit-note route, and delete-note route by adding the following code:

@app.route('/add-note', methods=['GET','POST'])
def addNote():

    if(request.method == "GET"):

        return "<p>Add note page</p>"

    elif (request.method == "POST"):
        # logic for adding a note

@app.route('/edit-note', methods=['GET','POST'])
def editNote():

    if(request.method == "GET"):

        return "<p>Edit note page</p>"

    elif (request.method == "POST"):
        #logic for editing a note

@app.route('/delete-note', methods=['POST'])
def deleteNote():

    # logic for deleting a note

The add-note route accepts two methods; GET notes while a user visits that page, and POST when a user submits a filled form containing details of a new note.

Same applies to edit-note; GET when the user visits the page, and POST when the user submits a form on the page.

At this point, we are only returning paragraphs for the pages. We need to return more visual content and handle the commented logic for the routes that do not return pages. We do this in the next step.

Adding logic and templates to the routes

First, we will import the render_template and redirect module from flask:

from flask import request,Flask,render_template,redirect

As the name suggests, render_template will load an HTML template file, and redirect will be called when redirecting from one route to another, particularly those that do not return pages.

Working on the templates

In the project root folder, create a folder and name it templates. Inside the folder, create a base.html file and add the following code:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<link
			rel="stylesheet"
			href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
			integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
			crossorigin="anonymous"
		/>

		<title>{% block title%} {% endblock %}</title>
	</head>
	<body>
		<nav class="navbar navbar-expand-lg navbar-light bg-light">
			<a class="navbar-brand" href="#">Notes app</a>
			<div class="collapse navbar-collapse" id="navbarSupportedContent">
				<ul class="navbar-nav mx-auto">
					<li class="nav-item {{ 'active' if homeIsActive == True }}">
						<a class="nav-link" href="/">Home</a>
					</li>
					<li class="nav-item {{ 'active' if addNoteIsActive == True }}">
						<a class="nav-link" href="/add-note">Add note</a>
					</li>
				</ul>
			</div>
		</nav>
		{% block content %} {% endblock %}
		<footer>
			<hr />
			<p class="text-center">Notes app</p>
		</footer>
	</body>
</html>

To enhance code reusability, all templates will be loaded from one base file. Furthermore, since Flask uses Jinja for templating, dynamic content will be loaded on each page.

According to the file above, only the title and the body content will be different for each page, and that is pretty much what we want in our simple application.

The home page route

In the templates folder, create a pages folder. Inside, create a home.html file and add the code below:

{% extends 'base.html'%} {% block title %} Home {% endblock %} {% block content
%}
<div class="container mt-5 mb-5 px-10 ">
	<div class="row">
		{% if (notes is defined) and notes %} {% for note in notes %}
		<div class="col-md-6 offset-md-3 mb-2 ">
			<div class="card">
				<div class="card-body">
					<p class="card-text text-muted">
						{{ note['createdAt'].strftime('%Y-%m-%d') }}
					</p>
					<h4 class="card-title">{{ note['title'] }}</h4>
					<p class="card-text">{{ note['description'] }}</p>
					<div class="d-flex justify-content-between">
						<a href="/edit-note?form={{ note['_id'] }}" class="btn btn-primary">
							Edit
						</a>
						<form method="POST" action="/delete-note">
							<input type="hidden" name="_id" value="{{ note['_id'] }}" />
							<button type="submit" class="btn btn-danger">Delete</button>
						</form>
					</div>
				</div>
			</div>
		</div>
		{% endfor %} {% else %}
		<div class="col-md-6 offset-md-3 text-center">
			<h4>You have not added any notes</h4>
			<a href="/add-note" class="btn btn-primary"> Add note </a>
		</div>
		{% endif %}
	</div>
</div>
{% endblock %}

In the file above, we are:

  • Extending the base file we had created previously.
  • Setting the title of the page.
  • Setting the body of the page, which comprises checking whether we have notes added. If we have, we loop through them, or we output a message and a call to action. For every note, we are showing its date of creation, its title, and description.

In app.py, edit the home route function as shown below:

@app.route("/")
def home():

    # get the notes from the database
    notes = list(mongo.db.notes.find({}).sort("createdAt",-1));

    # render a view
    return render_template("/pages/home.html",homeIsActive=True,addNoteIsActive=False,notes=notes)

In the home() function, we are:

  • Fetching the notes from the database; ordered in descending order.
  • Returning a view, passing along data. homeIsActive and addNoteIsActive for the navbar to set the active page, and notes for the data fetched.

To test this home route, make sure your application is running, and refresh the page on the browser. Since you do not have any added notes, you will receive a message and a call to action.

The add-note route

In templates/pages, create a file add-note.html. In this file, add the following code:

{% extends 'base.html'%} {% block title %} Add note {% endblock %} {% block
content %}
<div class="container mt-5 mb-5 px-10 ">
	<div class="row">
		<div class="col-md-6 offset-md-3">
			<form method="POST" action="/add-note">
				<div class="form-group">
					<label for="title">Title</label>
					<input
						type="text"
						class="form-control"
						id="title"
						aria-describedby="noteTitle"
						placeholder="Enter note title"
						name="title"
						required
					/>
					<small id="noteTitle" class="form-text text-muted"
						>E.g My new room.</small
					>
				</div>
				<div class="form-group">
					<label for="description">Description</label>
					<textarea
						id="description"
						name="description"
						class="form-control"
						placeholder="Some description"
						aria-describedby="description"
						required
					>
					</textarea>
					<small id="description" class="form-text text-muted"
						>E.g My new room number is 1234.</small
					>
				</div>
				<button type="submit" class="btn btn-primary">Submit</button>
			</form>
		</div>
	</div>
</div>
{% endblock %}

In the file above, we are;

  • Extending the common base.html file.
  • Setting the title.
  • Setting the body, which is a form with a title and a description field.

In app.py, edit the addNote() function as follows:

@app.route("/add-note", methods=['GET','POST'])
def addNote():
    if(request.method == "GET"):

        return render_template("pages/add-note.html",homeIsActive=False,addNoteIsActive=True)

    elif (request.method == "POST"):

        # get the fields data
        title = request.form['title']
        description = request.form['description']
        createdAt = datetime.datetime.now()

        # save the record to the database
        mongo.db.notes.insert({"title":title,"description":description,"createdAt":createdAt})

        # redirect to home page
        return redirect("/")

When we have a GET call in the function above, we are simply returning a view with navigation bar configuration variables.

When we have a POST call, we get the data submitted from the form, save it to the database, and redirect to the home page.

To test the functionality, ensure the development server is running, and refresh the add-note page. You should see a form, and when you fill and submit it, you will be redirected to the home page; now with a saved note.

The edit-note route

In the templates/pages folder, create a edit-note.html file. In the file, add the following code:

{% extends 'base.html'%} {% block title %} Edit note {% endblock %} {% block
content %}
<div class="container mt-5 mb-5 px-10 ">
	<div class="row">
		<div class="col-md-6 offset-md-3">
			<form method="POST" action="/edit-note">
				<input type="hidden" name="_id" value="{{ note._id }}" />
				<div class="form-group">
					<label for="title">Title</label>
					<input
						type="text"
						class="form-control"
						id="title"
						aria-describedby="noteTitle"
						placeholder="Enter note title"
						name="title"
						value="{{ note.title }}"
						required
					/>
					<small id="noteTitle" class="form-text text-muted"
						>E.g My new room.</small
					>
				</div>
				<div class="form-group">
					<label for="description">Description</label>
					<textarea
						id="description"
						name="description"
						class="form-control"
						placeholder="Some description"
						aria-describedby="description"
						required
					>
{{ note.description }}</textarea
					>
					<small id="description" class="form-text text-muted"
						>E.g My new room number is 1234.</small
					>
				</div>
				<button type="submit" class="btn btn-primary">Submit</button>
			</form>
		</div>
	</div>
</div>
{% endblock %}

In the file above, we extend the common base.html file, adding a title and a body content; simply a pre-filled form with a specific note’s data.

In app.py, edit the editNote function as follows:

@app.route('/edit-note', methods=['GET','POST'])
def editNote():

    if request.method == "GET":

        # get the id of the note to edit
        noteId = request.args.get('form')


        # get the note details from the db
        note = dict(mongo.db.notes.find_one({"_id":ObjectId(noteId)}))

        # direct to edit note page
        return render_template('pages/edit-note.html',note=note)

    elif request.method == "POST":

        #get the data of the note
        noteId = request.form['_id']
        title = request.form['title']
        description = request.form['description']

        # update the data in the db
        mongo.db.notes.update_one({"_id":ObjectId(noteId)},{"$set":{"title":title,"description":description}})

        # redirect to home page
        return redirect("/")

In the function above, we have two calls.

  • A GET call, where we get the note details from the database using its id, and render the form using those details.
  • A POST call, where we get the updated details from the form, and update the note from the database after which you are redirected to the home page.

To test this functionality:

  • Ensure the development server is running.
  • Refresh the home page.
  • For any note, click on edit, edit any field, and then hit Submit. The new details should be reflected.

The delete-note route

In app.py, edit the deleteNote as follows:

@app.route('/delete-note', methods=['POST'])
def deleteNote():

    # get the id of the note to delete
    noteId = request.form['_id']

    # delete from the database
    mongo.db.notes.delete_one({ "_id": ObjectId(noteId)})

    # redirect to home page
    return redirect("/")

From the function above, we are getting the id of the note to be deleted, deleting it from the database, and redirecting it to the home page.

To test this functionality:

  • Make sure the development server is running.
  • Refresh the home page.
  • For any note, click on delete; it should be deleted successfully.

Conclusion

In this article, we have built a simple notes app using Flask and MongoDB.

Here are some resources that will help you gain more insights into the covered technologies.


Peer Review Contributions by: Mercy Meave