Introduction to Building a Web Application with Flask
Posted on Aug 02, 2024 @ 03:55 PM under Flask Web Development Python
A Comprehensive Guide
Table of Contents
- What is Flask
- Setting Up Flask
- Creating Your First Flask Application
- Routes and Views
- Templates and Static Files
- Handling Forms and User Input
- A Better Flask Application Structure
- Working With Databases
- Conclusion
What is Flask
Flask is a web application framework written in Python, designed to make building and deploying web applications straightforward. It’s known for being a flexible micro-framework, which means it’s streamlined and minimalistic compared to full-stack frameworks that come with a lot of built-in features.
Flask focuses on providing just the essentials you need for web development, like managing sessions and handling requests. It doesn’t include extras like data handling by default, leaving those decisions up to you. This gives you the freedom to extend Flask with the exact features you need, without any extra bloat.
What makes Flask special is its simplicity and flexibility, making it a popular choice for creating web APIs and services. Its lightweight nature allows you to build powerful applications while keeping things as straightforward as possible.
Setting Up Flask
The first thing that you will need to do is to create a folder for the application. You can call it whatever you want, but I will call it myflaskapp
. This folder can be created with your file manager or using the command line. I am using Linux (commands should also work on a Mac and WSL on Windows), so I will use two commands below to create a directory and then change directory to the directory that I just created.
$ mkdir myflaskapp
$ cd myflaskapp
We also need pip
which is a Python package manager. This will enable us to install and update packages automatically in a virtual environment which will be discussed next. Installing and upgrading pip can be done using the following command:
$ python3 -m pip install --user --upgrade pip
NB: The -m
is used to run a specified module. In this case, it is used to run the pip
module
Another thing that is needed before installing Flask, is to create a virtual environment. This can be done on a number of ways on different platforms. One such installation is to use the command below:
$ python3 -m pip install --user virtualenv
The purpose of the virtual environment is to isolate the installed packages from the rest of the system packages. Although you can install all your packages globally, it is usually a good practice to isolate your packages, as doing this will minimize conflicts. This will become more evident as your application grows and you create more apps. In Linux, you can create a virtual environment and also activate it as shown below :
NB: The -m
will use the module venv
and env
will be the name of the folder that will house the environment files. You can give env
folder any name you desire. For the sake of this tutorial let us stick with env
for the environment folder.
$ python3 -m venv env
$ source env/bin/activate
After running the commands above, the command prompt should change to show that you are working in the virtual environment. It should look like this:
$ (env) myflaskapp$
Once we have our virtual environment activated, you can now install Flask. You can do this easily with pip
, Python’s package manager. in your terminal:
$ (env) myflaskapp$ pip install Flask
Basic Project Structure
For now, you need a basic structure for your flask application (This structure will be improved later in this article). In addition to the env
folder, we will create two folders, templates
and static
. Now the the structure inside the myflaskapp folder at this point should look like this (3 directories and 1 file):
├── app.py
├── env
├── static
└── templates
Creating Your First Flask Application
Let’s create a basic Flask application. Create a new file called app.py
and add the following code below:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return "Hello, Flask!"
if __name__ == '__main__':
app.run(debug=True)
In the app.py
, we begin by importing the Flask class from the flask module.
Next, an instance of the Flask class is created and assigned to the variable app. Flask(__name__)
is used to initialize the web application. __name__
is a special Python variable that gives Flask the name of the current module. This is useful for Flask to know where to look for resources and configurations.
We then create a route that is used to tell the application what should happen when a user visits a specific URL on your web application. In this case, @app.route('/')
which is a called a decorator, tells Flask to call the home()
function when someone accesses the root URL or starting point of your site. Think of @app.route('/')
as setting up a rule: "When someone visits the home page, do this.
Below the route, the home()
function is defined that will be called when url defined by the decorator is visited above. All it will do in this case, is to return the string "Hello, Flask!" to the browser.
__name__ == '__main__':
is a common Python idiom that ensures the code inside this block runs only if the script is executed directly, not if it is imported into another script.
The final line, app.run(debug=True)
starts the Flask web server in debug mode, which is important if you want functionalites that are helpful for debugging. By default, Flask will listen on port 5000.
Run this file with the command python3 app.py
as shown below, and visit http://127.0.0.1:5000/ in your browser. You should see "Hello, Flask!" displayed.
$ (env) myflaskapp$ python3 app.py
Routes and Views
Routes are the URLs that users visit in your app, and views are the functions that handle these URLs. In your app.py
, you’ve already defined a route with @app.route('/')
. You can add more routes by defining new functions and using different URL patterns:
@app.route('/about')
def about():
return "About Page"
If you refresh and test this route, you will see the returned string displayed in the browser. This is not very useful, so next, we will see how we can render a complete HTML page from our routing functions.
Templates and Static Files
To make our app more presentable, we can use HTML templates and static files (like CSS and JavaScript). Flask uses Jinja2 for templating. Create a folder named templates and add an HTML file called index.html (By default Flask looks for this templates folder, so make sure you manually create this folder ):
<!DOCTYPE html>
<html>
<head>
<title>My Flask App</title>
</head>
<body>
<h1>Welcome to My Flask App!</h1>
</body>
</html>
In your app.py
, the template can be rendered with the function render_template()
that will need to be imported. This is shown below in our revised home()
function. Also, the home()
function defaults to only processing GET
requests.
NB: A GET
request retrieves data from a server, while a POST
request sends data to a server to create or update something
If we wanted this function to be able to process GET
and POST
requests, we would need to adjust the route to @app.route('/', methods=['GET', 'POST'])
. This will be done when we learn how to process HTML input form data in the next section.
from flask import render_template
@app.route('/')
def home():
return render_template('index.html')
In Flask, handling static files—like images, CSS, and JavaScript—is straightforward and user-friendly. Flask automatically serves static files from a folder named static
within your project. When you place your static files in this folder, Flask makes them accessible through a URL path starting with /static/. For example, if you have a CSS file called main.css
in a folder named css
located in the static directory static/css
, you can include it in your HTML with a simple link like <link rel="stylesheet" href="/static/css/main.css">
. This way, Flask manages your static assets efficiently, letting you focus on building your app while it takes care of serving those important files.
Handling Forms and User Input
Another important aspect of web development is the processing of user data by form submission. In this context, forms will be HTML forms that will get a user's input and then send that data to the flask server. Let us update the index.html
with two fields, 'username' and 'email'. I have also included a little CSS styling that is placed in the <head>
tag between <style>
tag. This is used to center the content horizontally and vertically. Don't worry if you do not understand the CSS styling, you do not need to understand the details of how the styling works, to understand the processing of the form. Notice in the <form>
tag that the action="/"
, this is referring to the the '/'
route on our server. That means that the home()
will be called when the form is submitted. We will make changes to the home()
function to accommodate this POST
request as our function as it is, will only process GET
requests. The updated index.html
is shown below.
<!DOCTYPE html>
<html>
<head>
<title>My Flask App</title>
<style>
body, html {
height: 100%;
margin: 0;
}
.container {
display: flex;
justify-content: center; /* Horizontal alignment */
align-items: center; /* Vertical alignment */
height: 100vh;
background-color: lightgray;
}
.center-div {
width: 50%;
background-color: lightblue;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="center-div">
<h1>Welcome to My Flask App!</h1>
<h5>Submit Your Information</h5>
<form method="post" action="/">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required><br><br>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br><br>
<input type="submit" value="Submit">
</form>
</div>
</div>
</body>
</html>
After refreshing the page, you should see this:
The next thing to do is to update your '/'
route. The complete app.py
so far is shown below:
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/about')
def about():
return "About Page"
@app.route('/', methods=['GET', 'POST'])
def home():
username = ''
email = ''
if request.method == 'POST':
# Get the form data
username = request.form.get('username')
email = request.form.get('email')
return f"<h1 align='center'>Thank you {username}. Your email is: {email}.</h1>"
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
Now in the home()
function above, two variables are created: username
and email
at the beginning of the function. These variables will be used to store the username and email data that will be extracted from the request coming from the HTML form. We then use the condition: if request.method == 'POST'
to ensure we run the code to extract data only if we are processing the POST
method. To get the username and email data, the request.form.get()
method is used. After extracting the necessary data, we then return from the function. To make it simple, we just return a formatted string to the browser with the username and email as a part of the string. Since we are returning from the home()
function if there is a POST
request, this means, that the index.html
template will not be rendered. The rendering of the index.html
template will only occur when there is a GET
request. At this point, you should be able to refresh your page, enter a username and a password, and click submit. The browser should display the formatted string that is returned from the home()
function. With what we have done so far, you are in a good position to be able to start a very basic Flask project.
A Better Flask Application Structure
Before we go any further, there is a major issue with our structure so far. We have all our code in the app.py
. That is good to demonstrate how easy it is to get Flask up and running. However, with this structure, as our application grows, the management will be a nightmare. Structuring your Flask application is subjective. I typically structure my Flask applications in three main ways. Here, I'll present one approach that strikes a balance between simplicity and complexity, while making it suitable for production use. For more advanced setups. You can visit these github repositories: Basic Flask with HTMX Setup and Comprehensive Multi-App Setup.
For this demonstration, we will create a brand new application. After completing this refined structure, we will use it in the next section to demonstrate using it with a database.
The following commands will create a new folder, create a virtual environment, install the necessary packages, and then create empty folders and files to define the structure of our application. The purpose of each file and directory will be discussed as they get used in our application.
$ mkdir myflaskappV2
$ cd myflaskappV2
After navigating to the myflaskappV2
directory, create an app
folder, an empty config.py
and run.py
python file.
$~/flaskappV2$ mkdir app
$~/flaskappV2$ touch config.py
$~/flaskappV2$ touch run.py
Navigate to the app
directory and create the following files inside the app
directory: __init__.py
, models.py
, views.py
. While you are still in the app
directory, create the following folders: static
, templates
.
$~/flaskappV2$ cd app
$~/flaskappV2/app$ touch touch __init__.py models.py views.py
$~/flaskappV2/app$ mkdir static
$~/flaskappV2/app$ mkdir templates
Next create three (3) HTML files in the templates
folder: base.html
, index.html
, users.html
Also, create a main.css
in your static
folder.
Inside your flaskappV2
folder should now look like this:
├── app
│ ├── __init__.py
│ ├── models.py
│ ├── static
│ │ └── main.css
│ ├── templates
│ │ ├── base.html
│ │ ├── index.html
│ │ └── users.html
│ └── views.py
|── config.py
└── run.py
You should notice a special file __init__.py
in the app
folder. This file allows Python to recognize the app
directory as a python package. In other words, I can now import modules directly for this directory. Additionally, the __init__.py
file as the name suggests, is perfect for including the initialization code for the package. This also includes creating global objects that all modules in the package can have access to.
Before we start to add code to the files, let us create our virtual environment and install the necessary packages.
$ python3 -m venv env
$ source env/bin/activate
$ (env) myflaskapp$ pip install Flask
Let's start by adding code to our config.py
. We will create a basic class and then add a secret key. This secret key serves a number of purposes as it relates to security in Flask, for example, to sign cookies during session handling and provide CSRF protection during form submission. For configuration variables, the standard is to use uppercase letters as we will see throughout this article. Flask requires the secret key to be set by using this code app.config['SECRET_KEY'] = 'your_secret_key'
where you replace your_secret_key
with a long random string. We want our configuration variables to be separate from the main application, and that is why we use the config.py
for storing and setting our configuration variables as shown below.
class Config:
oSECRET_KEY = 'mysecretkey789'
In the code above, a basic class is used for setting the configuration variable. The only variable we have so far is the SECRET_KEY
. Feel free to replace the mysecretkey789
string with your own.
Next, let's add code to the __init__.py
file like so:
from flask import Flask
from config import Config
def create_app(config_class=Config):
# Create a new flask instance
app = Flask(__name__)
# read configurations
app.config.from_object(config_class)
return app
Remember that the __init__.py
can be used for the initialization of code for our package. Here, we define a function that creates a Flask object and returns it after making the necessary modifications. This is a common design pattern that is used in Flask applications to configure and initialize the application. More generally, this design pattern is referred to as the Factory Method. Writing the code this way has several benefits. One major benefit is that I can pass different configurations to this method for my application without changing my main application code. For example, I could have different configurations for testing, development, and production. I have defined my create_app
method to take in a configuration class that we will give it when we are calling this method. This class that will be passed, will define all the necessary configuration variables to run the application. We also set the default class to be taken from the config.py
class that we created if no argument is passed when calling the create_app
factory method. To use the Config
class, we had to import it using the code, from config import Config
. This import will find the config.py
module as it resides at the root of our application where Flask looks by default when the application is running. The app.config.from_object(config_class)
is used to set the configurations in app.config
by calling the from_object
method. Hence, app.config['SECRET_KEY']
will now be set to mysecretkey789
as defined in our config.py
file.
In order to call this method to start our application, we will write code in our run.py
as shown below:
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
We start by importing create_app
from the app
package. Next, we create an instance of the application. Notice we did not give it any arguments, so it will use our default configuration that was set if no arguments were given. The remaining code is code that we have seen before that is used to run our application.
We could run the application using python3 run.py
, but that would not be interesting as we haven't created our views as yet. Let's do that now.
Template Inheritance and Blueprints
In our basic setup, we created a basic index.html
file that had our entire HTML code. This is not effective, especially if we want to have a consistent layout/design throughout the application. The way provided by Flask is to use template inheritance. This can be accomplished by having a base layout that all the other pages will inherit. We can use the same HTML code that we had before, but structure it differently, to be able to easily create new pages without duplicating code. To enhance this process, Flask uses the Jinja2 templating engine that comes by default when installing Flask. This templating engine enables us to use placeholders for dynamic content. As a result, we will not only use pure HTML syntax, but our HTML files can now be mixed with syntax that can also manipulate variables and expressions. These additional syntaxes will be dynamically replaced when the final HTML is processed for rendering. So although you have additional syntax in your file, the final output to the browser will be pure HTML.
NB: The two jinja2 templates styntax that you will see here are: {% %} and {{ }}. The {{ }} is used to output the result of an expression to the template or to display variable information. The {% %} is used for control flow statements and logic like loops and conditionals
We could create a base layout base.html
like this:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %} {% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
</head>
<body>
<div class="container">
{% block content %} {% endblock %}
</div>
</body>
</html>
In the <head>
tag, we declare what is known as a block that we will give a name and also syntax to tell where the block will end. So the block by name title
will be replaced by any code that inherits or extends base.html
and declares this block. This will be demonstrated when we create our new index.html
. The same goes for the block by the name content
that is located in the container
div. For the styling of the div tags, we no longer keep the styles in the html file, but we created a main.css
file in the app/static/
folder and placed the CSS styling there. This means we will now have to link the base.html
file to that stylesheet using the code <link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
. Notice that instead of manually writing out the URL for the main.css
, we used the url_for
template function provided by Flask to get the exact URL for main.css
from the static directory.
The main.css
file is shown below.
body, html {
height: 100%;
margin: 0;
}
.container {
display: flex;
justify-content: center; /* Horizontal alignment */
align-items: center; /* Vertical alignment */
height: 100vh;
background-color: lightgray;
}
.center-div {
width: 50%;
background-color: lightblue;
text-align: center;
}
Inheriting the base.html
to create our index.html
is now straightforward. All we need to do is to extend the base.html
and then tell it what to put in the title
block and the content
. The content for the index.html
is shown below.
{% extends "base.html" %}
{% block title %} My Flask App {% endblock %}
{% block content %}
<div class="center-div">
<h1>Welcome to My Flask App!</h1>
<h5>Submit Your Information</h5>
<form method="post" action="/">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br><br>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br><br>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br><br>
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}
Notice all we are doing here is extending the base.html
and providing what content should be given to the two blocks (title and content) that we defined in base.html
.
Finally for this section, we can add code to our views.py
. This will be the file, where we will define all our routes. Before, adding the code, we need to understand another useful feature provided by Flask that is called Blueprints. Blueprints works perfectly with the Factory Method pattern we discussed above to ensure more modular and maintainable code. Blueprints provide us with the ability to break our application into smaller reusable components. For example, we can group all related routes for a particular use case. For our application, we will just create two Blueprints, one for our general routes and then one for the user-related routes. One thing to remember, is that after creating our blueprints, we have to register them in our factory method so that the application is made aware of them. The views.py
is shown below:
from flask import Blueprint, render_template
main_bp = Blueprint('main', __name__)
user_bp = Blueprint('users', __name__, url_prefix='/user')
@main_bp.route('/')
def index():
return render_template('index.html')
Naturally, we first have to import the Blueprint class from Flask. We then create two blueprints by the names main
and users
. The first blueprint did not use a URL prefix and thus will not add a prefix to the URL to call the index()
function. This means, the URL will be http://localhost:5000/
(you can omit the last /
). The second blueprint users
attaches a prefix and thus will be required in the URL to access the specified route. For example, if we have @user_bp.route('/profile')
, the link for this route would be http://localhost:5000/users/profile
. This is how we can group all routes related to user functionality under /users/
. By now, it should be clear that we no longer use routes app.route('/')
, but instead use the blueprint @main_bp.route('/')
. This makes sense as we no longer have access to the app instance.
NB If you must access the app instance, Flask also provides the current_app
proxy that can be imported and used to access the application context.
As was stated before, we now have to register the blueprints with our application. The updated __init__.py
is shown below:
from flask import Flask
from config import Config
def create_app(config_class=Config):
# Create a new flask instance
app = Flask(__name__)
app.config.from_object(config_class)
#Register blueprints
from .views import main_bp
from .views import user_bp
app.register_blueprint(main_bp)
app.register_blueprint(user_bp)
return app
We import our blueprints from the .views
( the dot is using relative imports, so Flask will look in the current directory or package ) then register our blueprint with app.register_blueprint
method.
Now we can run our application and should be presented with our form as before.
Working with Databases
In this section, we will connect to an SQLite database and store the username and password. This requires us to configure a database and also create a model that will represent a user table in our database. This is where our models.py
file comes in. We also need to add configurations for the database in our config.py
.
NB: You will notice that you might have an app/pycache folder that you did not manually create. This directory was automatically created to store compiled bytecode files.
For our database, we will use a simple database, SQLite, which is a small self-contained SQL database engine. To use this database, you do not need to install additional Python libraries as it is supported by default. However, we will use the library/extension Flask-SQLAlchemy
. Flask-SQLAlchemy
is an extension for Flask that integrates SQLAlchemy, a popular SQL toolkit and Object-Relational Mapper (ORM), with Flask. An ORM basically allows us to use classes to create and interact with our database instead of writing raw SQL statements. Make sure your environment is activated and then install Flask-SQLAlchemy
using pip
.
$ (env) myflaskappV2$ pip install Flask-SQLAlchemy
We could have used code to initialize our database when starting the application. However, a better approach is to use database migrations. For this, we will also install another extension Flask-Migrate
.
NB: Database migrations are a way to manage changes to your database schema over time. They allow you to make incremental updates, such as adding or modifying tables and columns, without losing existing data or disrupting your application. Migrations keep track of these changes, ensuring that your database schema evolves in a controlled and reversible manner.
$ (env) myflaskappV2$ pip install Flask-Migrate
To use the database we have to update our config.py
. The updated file is shown below.
class Config:
SECRET_KEY = 'mysecretkey789'
SQLALCHEMY_DATABASE_URI = 'sqlite:///mydata.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
For our application, we need only two (2) additional lines of configurations for Flask-SQLAlchemy
to work. The first variable SQLALCHEMY_DATABASE_URI
points to the name and location of the database that you want to create. This will be stored in the root of our application. The second variable (which is optional) SQLALCHEMY_TRACK_MODIFICATIONS
is set to False
. This is set to False
to disable Flask-SQLAlchemy's
feature that tracks changes to objects. This will save us memory and processing time.
Again we have to update our __init__.py
to instantiate the two new extensions that we installed above and also to initialize these extensions. The updated __init__
is shown below:
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# instantiate extensions
db = SQLAlchemy()
migrate = Migrate()
def create_app(config_class=Config):
# Create a new flask instance
app = Flask(__name__)
app.config.from_object(config_class)
# Initialize the extension libraries with application
db.init_app(app)
migrate.init_app(app, db)
#Register blueprints
from .views import main_bp
from .views import user_bp
app.register_blueprint(main_bp)
app.register_blueprint(user_bp)
return app
In the updated __init__.py
, we import the SQLAlchemy
and Migrate
classes.
Below all our imports, we then instantiate these classes to create objects. Instantiating these objects here, will ensure that they will be globally accessible to all the modules in our app
package. Initializing these extensions is done by using the init_app()
of the created objects. This method basically connects the extension to our app, so that they can have access to the application's configurations among other things. Notice that for the migrate
object, both the app
and the db
object are needed for initialization.
Our models.py
is where we will define the classes that will represent the tables in our database. For this application, we only have one class as shown below.
from . import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(30), nullable=False)
email = db.Column(db.String(255), nullable=False)
def __repr__(self):
return f'<User {self.name}>'
In the first line of code, we are importing db
from the package, since it is globally accessible, we import by using .
, which tells Flask to look in the current package.
After importing the db
object, we define the User
class that will extend the db.Model
class. It is necessary to extend this base class so that our User
class will have access to all the functionalities in the db.Model
class. This User
class will automatically be used to create a table by the name of user
in our database.
NB: If we do not specify a table name, Flask-SQLAlchemy will create a table name with the name of the class in lowercase. We could have specified the name of the class using a special variable: __tablename__ = 'user'
. However, this is not necessary for our use case.
Next, we define the attributes/variables in the class that will represent the fields in our user
table when it is created.
We first declare an integer id
as a column that will be set to the primary key. This will be auto-incremented for us. We then declare username
and email
which are both strings and should be null/empty. We also ensure that these fields are unique by setting unique=True
. The size of each field is dependent on the creator of the database, so you can change the size as you see fit. The sizes selected above should be adequate for most use cases.
We then define a special function (dunder/magic method) that is required if we want to print a human-readable string of our User
class.
We need to now set up our database so that our application can store and access user data. As stated before, we will make use of database migrations. This can be done by running a few commands in the terminal to initialize our migration environment, generate our migration script, and then apply the migration script.
Right now, if we run the migration commands, the User
model won't be recognized as it is not being called or used in our application. This means that the user
table won't be created. To ensure the User model is recognized, we need to import it into the views.py
file before running the database migration commands.
To import the User
class, we will update our views.py
to add the POST
data to the database from the HTML form that we created. By doing this, we are guaranteed that our application will be aware of the User
object.
from flask import Blueprint, render_template, request
from .models import User
from . import db
main_bp = Blueprint('main', __name__)
user_bp = Blueprint('users', __name__, url_prefix='/user')
@main_bp.route('/', methods=['GET', 'POST'])
def index():
username = ''
email = ''
message = ''
if request.method == 'POST':
# Get the form data
username = request.form.get('username')
email = request.form.get('email')
user = User(username=username, email=email)
db.session.add(user)
db.session.commit()
message = f'User {username} was saved successfully.'
return render_template('index.html', message=message)
In the views.py
we ensured that we imported the User
class and also the global db
object from our package. In addition to declaring the empty strings, username
and email
, we have now added another empty string message
. This string will store feedback that we can send to your render_template
function to be displayed in the index.html
template.
After getting the username
and email
data from the HTML form that was submitted, we use that data to create a user
object. We then add the user object to the database session (temporary workspace for changes) and then commit those changes (saves all changes in the session to the database permanently).
Before running our database migrations, let us quickly make a minor update to the index.html
to be able to display the message
string in the index.html
. We update it as follows:
{% extends "base.html" %}
{% block title %} My Flask App {% endblock %}
{% block content %}
o<div class="center-div">
o<h1>Welcome to My Flask App!</h1>
o <h5>Submit Your Information</h5>
o <form method="post" action="/">
o<label for="username">Username:</label>
o<input type="text" id="username" name="username" required><br><br>
o
o<label for="email">Email:</label>
o<input type="email" id="email" name="email" required><br><br>
o<p> {{ message }} </p>
o<input type="submit" value="Submit">
o </form>
o</div>
{% endblock %}
All we did was to use {{ }}
to display the content of the message
variable.
We will now run our first database migration to fully prepare our database. This requires us to run a few commands at the command line from the activated virtual Python environment. This means if are running your application, you need to stop it by running CTRL+C
to quit. The first command to run is shown below:
$ (env) myflaskappV2$ flask db init
This command initializes the migration environment by creating a migrations/
folder at the root of your project's directory. The arguments to the flask
command are available because we installed the Flask-Migrate
extension. Since we have made changes to our models.py
by creating a User
class, we need to run a command so that a migration script along with the configurations in the migrations/
folder are created.
$ (env) myflaskappV2$ flask db migrate -m "My First Migration"
After the generation of the migration script, we need it to be applied to the database. The command is given below to achieve this.
$ (env) myflaskappV2$ flask db upgrade
After running those three (3) commands above, you should have two new folders. You will have an instance/
folder that will have your database mydata.db
as configured in your config.py
. Since we are using SQLite
the database will be stored in the instance/
folder. If we had used a database like PostgreSQL
or MariaDB
, this folder would not be necessary. Also, if you are using earlier versions of Flask-Migrate
you might not see the instance/
folder, but just the database mydata.db
. Your complete file structure should more or less look like this:
├── app
│ ├── __init__.py
│ ├── models.py
│ ├── __pycache__
│ │ ├── __init__.cpython-312.pyc
│ │ ├── models.cpython-312.pyc
│ │ └── views.cpython-312.pyc
│ ├── static
│ │ └── main.css
│ ├── templates
│ │ ├── base.html
│ │ ├── index.html
│ │ └── users.html
│ └── views.py
├── config.py
├── env
├── instance
│ └── mydata.db
├── migrations
│ ├── alembic.ini
│ ├── env.py
│ ├── __pycache__
│ │ └── env.cpython-312.pyc
│ ├── README
│ ├── script.py.mako
│ └── versions
│ ├── e3386e2930e1_my_first_migration.py
│ └── __pycache__
│ └── e3386e2930e1_my_first_migration.cpython-312.pyc
├── __pycache__
│ └── config.cpython-312.pyc
└── run.py
Remember that the __pycache__
folders are created automatically and will store the bytecode for your compiled Python files. Another command that is available, but was not used here is flask db downgrade
. As you might have guessed it, this is used to revert the database migrations. In other words, it will undo/rollback the last changes, setting the database to the previous state.
There are two main ways how we can check if your tables were actually created in mydata.db
. We can use the sqlite3
to connect to our database or we can install a GUI database application like 'DB Browser for SQLite'. You can use thesqlite3
command if you have it installed like so:
myflaskappV2/instance$ sqlite3 mydata.db
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> .tables
alembic_version user
sqlite>
Note that this does not require your virtual environment, so you can just open another terminal, navigate to where your mydata.db
file was created, and connect to it by running sqlite3 mydata.db
. The .tables
command will list all your tables. In the database we have the migration information table alembic_version
and also our user
table. To see the schema or structure for the user table, we can run the .schema user
command in the database prompt:
sqlite> .schema user
CREATE TABLE user (
id INTEGER NOT NULL,
username VARCHAR(30) NOT NULL,
email VARCHAR(255) NOT NULL,
PRIMARY KEY (id),
UNIQUE (email),
UNIQUE (username)
);
NB When finished with the database, you can use .exit
or simply CTRL+D
to quit.
At this point, you can run your application and save data to the database. You should also get a message feedback when you create and save a user to the database. You can check the database to see if the database was updated as shown below:
sqlite> select * from user;
1|kevin|kevin@gmail.com
sqlite>
Our final functionality will be to be able to display the users from the database. The final update to our views.py
is shown below:
from flask import Blueprint, render_template, render_template_string, request
from .models import User
from . import db
main_bp = Blueprint('main', __name__)
user_bp = Blueprint('users', __name__, url_prefix='/user')
@main_bp.route('/', methods=['GET', 'POST'])
def index():
username = ''
email = ''
message = ''
if request.method == 'POST':
# Get the form data
username = request.form.get('username')
email = request.form.get('email')
user = User(username=username, email=email)
db.session.add(user)
db.session.commit()
message = f'User {username} was saved successfully.'
return render_template('index.html', message=message)
@user_bp.route('/allusers')
def allusers():
users = User.query.all()
return render_template('users.html', users=users)
To accomplish displaying the users from the database, we define the allusers()
function with the /allusers
route. Since we are using blueprints, then the url will be http://localhost:5000/user/allusers
. The good thing is that because we automatically have access to the url_for
function in all our templates, we can use that to generate the url associated with this function instead of typing it out.
NB: The url_for
function can also be used in our route functions by importing it and using it especially for redirecting to urls. However, there is no need to do this for this simple application example.
The allusers
function is simple. All we are doing here is to get all users from the User
table. The line, users = User.query.all()
will be translated into an SQL similar to SELECT * from user
. The difference however, is that it will return list of User
objects. You might not see a big difference now, but when your SQL statements get way more complex, it becomes very convenient. Using an ORM offers many additional benefits. For example, we can easily switch to another relational database that is supported by Flask-SQLAlchemy
, by just making changes to config.py
. In the render_template
function, we pass the list of users to the users.html
template. The users.html
template is given below (Don't panic! There is some new syntax that will be explained below.).
{% extends "base.html" %}
{% block title %} All Users {% endblock %}
{% block content %}
<div>
<h1>All Users ({{ users | length }})</h1>
<br />
{% if users %}
<ol>
{% for user in users %}
<li>{{ user.username }} ({{ user.email }})</li>
{% endfor %}
</ol>
{% endif %}
</div>
{% endblock %}
I know the users.html
has quite a bit of syntax that is being introduced here, but bear with me as I explain.
We already know the purpose of extending the base.html
and using the title
and content
blocks to add HTML content to display. We also know that the {{ }}
and {% %}
syntax is basically used for displaying variables/expressions and for control flow respectively. When we use {{ users | length }}
, the result of this expression will give us the length of the users
list that is passed from the allusers
function in views.py
. The |
is referred to as a filter in Jinja2, which is used to modify or process the data before it is displayed in the template. Hence, the length
filter calculates the number of items in the list. The result of using this filter will be for example be, <h1>All Users (5)</h1>
, if there are 5 users in your database. The next thing that will be done is to check if there are items in the users
list before we try to process the list. We use an if
statement to do this. Inside the if
statement we create an ordered list, then we use a for loop to display each user information. Similarly to the use of endblock
, we have to end our if
statements {% endif %}
and also our for
loop {% endfor %}
.
Before finally testing our application, we will make one more change to the index.html
. We need to create a link that will direct us to the allusers
function that will render users.html
template. We can do this by using the url_for
command in the index.html
to call the allusers
function. The main thing to remember is that since we are using Blueprints, we have to include the name of the Blueprint when calling the allusers
function. The name of our users blueprint is users
. We can create the link like this: <a href="{{url_for('users.allusers')}}"> View Users </a>
. The final index.html
is shown below:
{% extends "base.html" %}
{% block title %} My Flask App {% endblock %}
{% block content %}
<div class="center-div">
<h1>Welcome to My Flask App!</h1>
<h5>Submit Your Information</h5>
<form method="post" action="/">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br><br>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required><br><br>
<p> {{ message }} </p>
<input type="submit" value="Submit">
</form>
<h1><a href="{{url_for('users.allusers')}}"> View Users </a></h1>
</div>
{% endblock %}
Since we now know how to create a link to a view function that will render a new page, we should also update the users.html
to be able to link back to your form using something like this: <a href="{{ url_for('main.index') }}"> << Back to Form</a>
. The <<
symbol before the word 'Back' is used purely as a visual indicator to resemble a back icon. We can update the users.html
like this:
{% extends "base.html" %}
{% block title %} All Users {% endblock %}
{% block content %}
<div>
<h1>All Users ({{ users | length }})</h1>
<h3><a href="{{ url_for('main.index') }}"> << Back to Form</a></h3>
<br />
{% if users %}
<ol>
{% for user in users %}
<li>{{ user.username }} ({{ user.email }})</li>
{% endfor %}
</ol>
{% endif %}
</div>
{% endblock %}
You can now run the application and test it. Always remember, that you need to ensure that you have activated your virtual environment before running the application.
Below are images of the final result after adding 5 users.
The complete code is available here GitHub Link. The repository will also have a requirements.txt
file included. After cloning this repository and activating your virtual environment. You can run the command pip install -r requirements.txt
to install all the required packages. I created this requirements.txt
file by using the command in my virtual environment pip freeze > requirements.txt
. What this command does, is to save a snapshot of my environment's packages and their versions into a requirements.txt
file. This will allow you to recreate the same environment by installing the exact packages and their versions in your virtual environment.
Conclusion
In this article, I began by discussing Flask as a versatile web framework. We then set up Flask and organized a basic project structure for clarity. Next, we created a simple Flask application, defining routes and views to manage web requests, and utilized templates and static files for rendering content and serving assets. I also covered handling forms and user input, demonstrating how to post data to the server for processing. Additionally, we improved the application's structure by implementing template inheritance and Blueprints for better modularity. Finally, we explored how to integrate databases for dynamic data storage and retrieval.