Building a Python Note Application Using Flask and MongoDB
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. <!--more--> 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
andaddNoteIsActive
for the navbar to set the active page, andnotes
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