I am planning to do a series of posts where we will progressively build a demo ecommerce website using Flask, SQLAlchemy and React. The boilerplate repositories prepared for these posts might serve as useful starter kits for others. Plus it will help the reader get a feel of building a production grade website from scratch.
These posts will be based on some of my learnings obtained from building an ecommerce website for the past 6 years. Since I mostly worked on backend apis, devops and analytics — my posts will be focused more on those areas. But I will also try to post a little on frontend tools as well, though my experience there is comparatively limited.
In the first post of the series, we are just going to create a bare minimum flask app in the app factory pattern. The objective is just to build a skeleton architecture which would be able to accomodate the various types of services we will experiment with in the future posts.
It is assumed that the reader has a basic working knowledge of Flask and SQLAlchemy. If not, please read through Flask Quickstart and Flask-SQLAlchemy Quickstart once. These examples use the simple pattern where a flask application is instantiated and routes registered — all in a single module. While this is suitable for very small applications (in terms of lines of code), for most applications we will need a more modular and scaleable architecture when deploying to production. Flask provides various tools to accomplish this. We are primarily interested in Blueprints and the app factory pattern. You should read the official docs explaining blueprints and app factories as well once.
Checking out the boilerplate repo
I am planning to write the accompanying examples for all these posts as boilerplates — which can serve as useful starting points for your own apps. The code for the boilerplate app for this particular post is available here. Please clone it before you start reading this post. Or at least keep the github link open to follow along.
Structure of the app
The folder structure looks like this
I will cover testing in a separate post. So the above structure does not include tests directory. Also note that the
instance folder mentioned above, won't actually be available when you clone the repo. It is included in .gitignore to keep it out of version control. It is meant to store the non-shareable configuration details of the app (like passwords, secret keys etc)
Here is the .gitignore used
Before we start reviewing the code, let’s review the other setup steps required — like installing the packages, setting up the db etc.
Setting up mysql
For this project we are going to use mysql as the database. You can refer to various tutorials available online for the same.
For example, you can use this tutorial to setup a mysql server on an ubuntu machine, and this tutorial for creating a database and granting access to a particular user. Note the information like db username, password and the database name. You will need that later when creating the config file for the app.
Setting up the instance config file
Create a folder named
instance inside the cloned repo. As mentioned earlier, this will remain outside version control. Then create a file named
application.cfg.py like this
DB_USERNAME = 'admin'
DB_PASSWORD = 'adminpasswd'
DB_SERVER = 'localhost'
DB_NAME = 'flask_boilerplate'SECRET_KEY = "bf23423e58727fcdfdsgsagg681d9ewrrewr234346"
SECURITY_PASSWORD_SALT = "40113095-16f5-4e85-8140-98234802938320498"
The secret key and the password salt used above are just random strings. I use the python function `uuid.uuid4()` to generate these strings
The instance folder serves multiple purposes
- Being out of version control, it can safely contain secret keys
- It can also be used to store the variables which need to change between different installations of the app. We will change all the above keys when the app is deployed to production. So a different instance/application.cfg.py will be used when we deploy.
Setting up the virtualenv and installing the requirements
When working with python apps, it is always preferable to install the packages inside a virtual environment.
We can use virtualenvwrapper to set up the python environment for running the app. Create a virtualenv like this
mkvirtualenv -p python3 flask_boilerplate
And with the virtualenv active, install the requirements using
pip install -r requirements.txt. Here is the
requirements.txt for this project
With the environment set, we can start reviewing the server code.
The main script — server.py
The application server can be started by calling
python server.py. Here is the code
That’s all. The
app_factory.create_app function is the only entry point for our application. The entire architecture of the application can be understood by working backwards from here. Note that the
application object created here, will also be used by us later when we run the app using
uwsgi and serve it behind a
nginx proxy. But more on that in the next post.
The app_factory module
This is the heart of the application. It is placed in
This function will be invoked not just by
server.py, but by any number of other scripts which need an application instance to work with. As we can see in the above example, the
create_app function creates a flask app instance, sets some config variables on it, registers some blueprints, initializes some services and returns the ready to use app.
instance_relative_config set to True in the first line allows us to read the application's config information from inside the instance folder. We initialize the config with the variables defined in
app/default_config.py. This can include all the values which can be version committed. For secret keys, I prefer to initialize them to empty strings here, so that the app won't break even if the keys are absent in
The next line
app.config.from_pyfile can load the python file with the specified name present in the instance folder.
Now with all the database credentials loaded from the config file, we then initialize the db handler, register the blueprints and initialize the Flask-Security extension which is used to provide support for user registration and login.
The model layer
For the purpose of this example, I have only defined the models required by FlaskSecurity for user authentication. Here is
This pattern is straightaway taken from the Flask-Security quickstart example. This library requires a
Role model also to be specified for specifiying various roles for users like administrator, editor etc. We are not using roles in this example though. The
user_datastore created here is then used in the
create_app method when registering the Security module.
As mentioned earlier, we are using the blueprints pattern to modularize the urls. In our app, we are classifying the urls into 2 categories — pages and api — defined in
views/main_api_bp.py respectively. The former is supposed to render html responses while the latter will render json responses.
from flask import Blueprint, render_templatemain_pages_bp = Blueprint('main_pages_bp', __name__)@main_pages_bp.route("/")
And here is
from flask import Blueprint, jsonifymain_api_bp = Blueprint('main_api_bp', __name__)@main_api_bp.route("/")
The template files specified in the render_template method used in the views need to be present in the
templates folder. We can create a standard layout which can be extended by each page
The various concepts used in the above template — like import, block, macros, include etc are explained in detail in the Jinja2 documentation The include statements are basically partial templates which can be defined in separate files and loaded in other templates. The blocks are sections which can be overridden by any child template. Macros are similar to functions. The various partial templates defined in
app/templates/layout_sections can be explored directly in the repository code. In
macros.html, some utility functions have been defined like this
Now the template for home page — index.html is defined as follows
Thus we have an example of how to inherit from a layout and override specific blocks.
In flask, the folder named as
static is assumed to be the static folder meant to store and serve assets by default. The name of the folder can be customized if required, but there is no reason to do so usually. So here we have
app/static folder with js and css files in it. These files were included in the templates using the
macros.static_css_tag as shown above.
I have just placed some alert messages in the static code — just to illustrate that those files have been loaded. In further tutorials we will further explore how to bundle these static assets together while deploying to production.
The migration script — dbmanager.py
Now that we have reviewed the contents of the
app folder, we are ready to run the application. But we need to create the database tables prior to that. While smaller flask-sqlalchemy projects would have just used the
db.create_all method defined on the FlaskSQLAlchemy library for this, for a larger project we need a more scaleable method.
Typically, for large website projects, we need a database migration tool to manage the schema changes. The database schema will keep changing through the lifetime of the project — with constant addition, modification and deletion of various tables and constraints. Web frameworks handle this by using db migration tools — which have a chronological versioning mechanism. These tools will generate migration scripts, with each migration script having a reference to its immediate predecessor. Thus at any point in future, anyone who checks out the project, can just run the migrations scripts in order and get the database to the current schema. This is a very crucial requirement when multiple developers are working on the project.
In our case, we are going to use a library called
flask_migrate to generate and manage the migrations. This is based on the
alembic library which is the standardly used migration framework for SQLAlchemy.
The migration script looks like this
You can consider this as boilerplate code. We are creating a
Manager instance using flask script - which is basically an interface for generating command line utilities for flask. We are then registering our app in the manager. You can see how the app_factory pattern was useful here. It let us generate an instance of the app for the migration tool to introspect the registered models. We then add a command called
db which uses flask_migrate. The above code automatically ensures that the following commands are readily available
python dbmanager.py db init - To generate the
migrations folder for the first time
python dbmanager.py db migrate - To generate the migration scripts whenever we make any changes in our models
python dbmanager.py db upgrade - To apply the generated migrations in the chronological order so that the database reaches the correct schema state
python dbmanager.py db downgrade - To downgrade the database schema to a previous version - thus rolling back the changes.
migrations folder is auto-generated by running
python dbmanager.py db init. Whether to version commit this folder or not is a choice for the developer to make. I prefer version committing it, so that others can get started by running just
python dbmanager.py db upgrade
So let’s just run
python dbmanager.py db upgrade. You will be able to see a message showing that the migration was applied successfully.
Running the server
Now that everything is ready, the development server can be initialized by running
You can register an user account by clicking on the “Register” link and signing up. Then you can login using those credentials. The login and register pages don’t have any styling as they use the default html templates defined by the flask_security library. In the next posts, we can see how to customize these templates to use our layout.
The last remaining script left to review is
This is equivalent to the management shell offered by frameworks like Django. I have kept it very simple for the sake of this tutorial. But we can add a lot more features here as we will see in subsequent posts
To understand the reasoning behind second and third lines, you need to understand the concept of application contexts and request contexts in flask and how and why a request context needs to be pushed in the shell as explained here
But these are pretty advanced topics. So for the sake of this tutorial, just assume it as boilerplate code.
We can use this tool by calling the script like this and fiddling around with various objects like this
(myvenv) surya@devbox:~/personal/flask_pattern_minimal_with_auth$ python cli.pyIn : models.User.query.count()Out: 2In : models.User.query.all()Out: [email@example.com, firstname.lastname@example.org]
In the first post of this series, we have built a very basic flask app using app_factory pattern. It does nothing except to let you register an user account and login. And it lays out the the skeleton of various concepts like templates, static folders etc which will be further explored in the subsequent posts.
Originally published at https://techonometrics.com on April 15, 2020.