A Flask Tutorial in few minutes with SQLite, SQLAlchemy and Jinja2

Julio Souto
7 min readNov 14, 2021
Flask’s logo

Flask is a web framework written in Python and it’s based on Jinja2 Template Engine and Werkzeug WSGI Toolkit.

It’s very fast for someone (or a team) to design and develop a minimalist web application using Flask, compared to other frameworks out there.

Today we are going to build a simple web application, which can take you only a few minutes.

This web application is going to let anybody add and visualize comments anonymously, which obviously doesn’t have many real world uses, actually having the purpose of showing how a very basic web application works using Flask and how fast it is to develop it.

Installing Flask

Using a Virtual Environment or not (which is not covered by this tutorial), go to your Command Prompt with Python already installed in your system and type:

pip install Flask

Create a directory for your web application

Create a directory for this project named “flask_app” or any other name you prefer.

Creating a basic web app

Inside your project’s directory, create a new file named “app.py”. If you intend to run this on the cloud — like PythonAnywhere — , it’s important to name the main module as “app.py” or “application.py”, since they might not work properly if you don’t.

From here on, all snippets will be placed inside the “app.py” file.

Importing the packages that will be used

from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy

Creating the Flask app

app = Flask(__name__)

Configuring the path to the database

SQLite will be used here. Attention to the 3 slashes before the DB file name. It means a relative path will be used.

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'

Creating a SQLAlchemy object — the dabatase object

SQLAlchemy is an open-source SQL toolkit and object-relational mapper for Python.

db = SQLAlchemy(app)

Designing the Database

This object oriented piece of code will define how the Database should be structured. The details of it will not be described here, since the code quite intuitive.

class Data(db.Model):    __tablename__ = 'data'    id = db.Column(db.Integer, primary_key=True)
comment = db.Column(db.Text, nullable=False)
# When doing a query, display each row represented
# by an object containing what's in the return statement
def __repr__(self):
return 'Comment ' + str(self.id)

Create the DB, based on the model above

db.create_all()

Define which HTML file should be rendered when the user access the index page

The HTML file should be in a subdirectory named “templates”, but this will be covered later on, after the back-end.

@app.route('/')
def index():
return render_template('index.html')

Define how the Comments page should behave for showing and saving user’s comments

POST and GET methods behave differently and GET is implicit. Note that if the method is GET, we send the object “comments” as a parameter to the HTML page, so all the comments in the DB can be accessed by index, as well as each property of each comment. E.G.: “comments[0].comment”.

@app.route('/comments', methods=['POST', 'GET'])
def comments():
if request.method == 'POST':
db.session.add(Data(
comment=request.form['comment'],
))
db.session.commit()
return redirect('/comments')
# Get all the posts from the Database
# This only happens if the method is GET (works like an "else")
comments = Data.query.all()
return render_template('comments.html', comments=comments)

Editing a comment

The ID of the row in the DB will be received from the HTML page and passed as an argument to the edit function. The GET method is implicit again.

Here, the parameter is “comment” (singular word) when the method is GET. That means only one comment object is being passed to the HTML page.

@app.route('/comments/edit/<int:id>', methods=['GET', 'POST'])
def edit(id):
comment_obj = Data.query.get(id)
if request.method == 'POST':
comment_obj.comment = request.form['comment']
db.session.commit()
return redirect('/comments')

return render_template('edit.html', comment=comment_obj)

Deleting a comment

The function “delete” works similarly to the function “edit”, but instead of updating a column, you delete the row in the DB.

For production cases, it would be great to only disable and do not show the comment, instead of deleting the row, since deleting could affect models built over the data. But that’s another story.

@app.route('/comments/delete/<int:id>')
def delete(id):
comment_obj = Data.query.get(id)
db.session.delete(comment_obj)
db.session.commit()
return redirect('/comments')

Making sure the Flask app will run when the app.py file is called

The debug parameter means it will be possible to see the errors, if any.

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

This is the end of the “app.py” file.

The front-end

Creating the “templates” directory

By default, Flask will look for the “base.html”, “index.html”, “comments.html” and “edit.html” pages (among any other html files) in the “templates” directory, inside your app’s directory.

So, you must create a “templates” directory, where the HTML files will be placed. E.G.: ../flask_app/templates/index.html.

The base template

The most powerful part of Jinja is template inheritance. Template inheritance allows you to build a base “skeleton” template that contains all the common elements of your site and defines blocks that child templates can override.

The base template is a HTML file that contains some blocks of code that can be reused in multiple other HTML files. That improves productivity and promotes standardization across all the web pages.

In the “templates” directory, create a new file named “base.html” and place the code below inside it. Note the Jinja2 showing up here.

<!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">
{% block head %} {% endblock %}
</head>
<body>
{% block body %} {% endblock %}
</body>
</html>

The index page

In the “templates” directory, create a new file named “index.html”.

Inside the file, you can write down something like the code below. As you can see, one can focus on the page itself and its content, since the most basic code is inherited from the base.html file with the support of Jinja.

{% extends 'base.html' %}{% block head %}
<title>Home Page</title>
{% endblock %}
{% block body %}
<h1>Home Page</h1>
{% endblock %}

The comments page

In the “templates” directory, create a new file named “comments.html”. Again, the base template will be reused in this file.

The comments web page will be able to display the comments added to the DB and to, of course, add new comments, since the DB will be empty in the beginning.

Add the code below to this HTML file:

{% extends 'base.html' %}{% block head %}
<title>Comments</title>
{% endblock %}
{% block body %}
<h1>All Comments</h1>
<hr>
<h2>Add a new comment:</h2>
<form action="/comments" method="POST">
Comment: <input type="text" name="comment" id="comment">
<br>
<br>
<input type="submit" value="Post">
</form>
<hr>
{% for comment in comments %}
<div>
<h2>{{ comment.id }}</h2>
<p>{{ comment.comment }}</p>
<a href="/comments/delete/{{comment.id}}">Delete</a>
<a href="/comments/edit/{{comment.id}}">Edit</a>
{% endfor %}
{% endblock %}

The comments object received from the back-end can be accessed and iterated using Jinja2. E.G.: “{% for comment in comments %}”, as well as each property of each item on it.

Using a simple form, it’s also possible for anyone to add new comments and display them straight away.

The Edit page

In the “templates” directory, create a new file named “edit.html” and place the code below inside it.

Note again the base template is reused, along with a simple form. One difference in the form is that now there is a default value for the input field, which is the original comment itself being edited.

{% extends 'base.html' %}{% block head %}
<title>Edit a Comment</title>
{% endblock %}
{% block body %}<hr>
<h2>Editing the comment:</h2>
<form action="/comments/edit/{{comment.id}}" method="POST">
Comment: <input type="text" name="comment" id="comment" value="{{comment.comment}}">
<br>
<br>
<input type="submit" value="Save">
</form>
<hr>
{% endblock %}

Running the web app

Run the “app.py” file and navigate to “http://127.0.0.1:5000” and the index page will be displayed.

Go to “http://127.0.0.1:5000/comments” and you will see something like:

The comments web page with the section to add a new comment

As soon as you add new comments, the comments web page is fed:

Two added comments being displayed with the options to edit and delete

If you click to edit the comment, then the edit.html page is displayed with the comment itself:

Editing a specific comment

Full code for the “app.py” file:

from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
db = SQLAlchemy(app)

class Data(db.Model):
__tablename__ = 'data' id = db.Column(db.Integer, primary_key=True)
comment = db.Column(db.Text, nullable=False)
# When doing a query, display each row represented
# by an object containing what's in the return statement
def __repr__(self):
return 'Comment ' + str(self.id)

db.create_all()

@app
.route('/')
def index():
return render_template('index.html')

@app
.route('/comments', methods=['POST', 'GET'])
def comments():
if request.method == 'POST':
db.session.add(Data(
comment=request.form['comment'],
))
db.session.commit()
return redirect('/comments')
# Get all the comments from the Database
# This only happens if the method is GET (like and "else")
comments = Data.query.all()
return render_template('comments.html', comments=comments)

@app
.route('/comments/edit/<int:id>', methods=['GET', 'POST'])
def edit(id):
comment_obj = Data.query.get(id)
if request.method == 'POST':
comment_obj.comment = request.form['comment']
db.session.commit()
return redirect('/comments')
else:
return render_template('edit.html', comment=comment_obj)

@app.route('/comments/delete/<int:id>')
def delete(id):
comment_obj = Data.query.get(id)
db.session.delete(comment_obj)
db.session.commit()
return redirect('/comments')
if __name__ == '__main__':
app.run(debug=True)

And that’s it! This is one of the reasons why Flask is widely used and accepted as a good framework to work with: it provides a minimalist and lightweight way to serve web pages with high productivity.

If you would like to put your own website online using the best and simplest web hosting platform out there with an integrated development environment, check out PythonAnywhere.

--

--

Julio Souto

I am something between a Software Engineer and a Solutions Architect, leading the Innovation department for DSV Brazil.