loading...

Flask Boilerplate - Structuring Flask App

idrisrampurawala profile image Idris Rampurawala Updated on ・8 min read

Flask is a lightweight WSGI web application framework. It is part of the categories of the micro-framework with little to no dependencies on external libraries. It doesn't enforce any dependencies or project layout. Because of which you will find plenty of different project layouts out there.

While I started working on Flask, I got confused to choose the correct layout for my applications. In this post, we will discuss some of the important points to keep in mind while structuring a Flask application with the help of my Flask Boilerplate project.

GitHub logo idris-rampurawala / flask-boilerplate

A Flask Boilerplate to quickly get started with production-grade flask application. It has some additional packages pre-configured for ease of development.

📝 NOTE

  • This boilerplate was created based on my cumulative knowledge gained by writing multiple flask projects
  • This may not be the choice of layout for your app as you may have different requirements but a great way to kick start a Flask app and add packages as per the need
  • It is lightweight in that it only has minimal packages to achieve the necessities of an app. But you can surely customize it based on your need 😉

Why Boilerplate? 🤔

When I started working on a flask application for the first time, I had a tough time figuring out the correct structure and packages to achieve the necessities of my flask app. Hence, I decided to make this very basic lightweight boilerplate available for the community while I figured out the structure working on multiple live projects in the past.

When we start a project from scratch, especially the backend services, there are some basic functionalities that it should possess. The following are some of the points in building up an efficient app explained concerning this flask boilerplate.

1. Folder Structure

Flask applications generally have many variations in their folder structure. The choice of keeping files in different folders is dependent on an individual/team/project type. On a high level, flask application can be created even with a single page or via a factory pattern method. Let's talk a bit more about the concepts here:

  • Factory Pattern
    Well-structured web apps separate logic between files and modules, typically with separation of concerns in mind. This seems tricky with Flask at first glance because our app depends on an app object that we create via app = Flask(__name__). Separating logic between modules means we would be importing this app object all over the place, eventually leading to problems like circular imports. The Flask Application Factory refers to a common pattern for solving this dilemma. The reason why the Application Factory is so important has to do with something called Flask's Application Context. Hence, we are making use of factory pattern inside this boilerplate project to make our lives a bit easier.

  • Blueprints
    In a normal project, the number of views, and templates, and models, and forms, etc. will grow. You can (hackily) separate them, but wouldn’t it be nice to have something that groups related views, templates, models, and forms? Blueprints! They are a great way to modularly compose Flask applications, which will help scale up projects.

Well, in this boilerplate, there's one blueprint core serving as a base and more can be added later as shown below:

# app/__init__.py
def create_app():
    ...
    app = Flask(APP_NAME)
    ...
    from .core.views import core as core_blueprint
    app.register_blueprint(
        core_blueprint,
        url_prefix='/api/v1/core'
    )

So, the folder structure looks as 👇

├── app
│   ├── core                # blueprint
│   │   ├── __init__.py
│   │   ├── constants.py
│   │   ├── enums.py
│   │   ├── tasks.py        # celery tasks specific to this blueprint
│   │   └── views.py
│   ├── config.py
│   └── __init__.py         # app initialization logic
├── .env
├── .gitignore
├── Pipfile
├── Pipfile.lock
├── README.md
├── authentication.py
├── celery_worker.py
└── run.py

2. Managing External Packages

One of the most important steps is to create a virtual environment for python projects to manage dependencies. There are various python packages for this purpose such as virtualenv, pyenv, etc. This boilerplate uses pipenv to address all the issues that I have faced using other packages for managing dependencies. Pipenv automatically creates and manages a virtual environment for your projects, as well as adds or removes packages from your Pipfile as you install/uninstall packages.

This boilerplate has those packages which are required to build up an app and hence you can always customize based on your need 😉

3. Workspace Setup

Though not directly related to an app, your workspace setup does affect your app creation process. Hence, setting up a workspace is a major step to speed up your working environment. I have written a post that specifically talks about setting up a python workspace in a visual studio code editor which you can refer and quickly get started ✌️

4. Configuration

Configuring a Flask app (or maybe any app) generally requires a lot of effort (if we are not experienced). In general, there can be 2 aspects to it -

  • Storing secrets securely
    Usually, a lot of secrets makes up a project such as DB credentials, API keys, etc. It is recommended to keep these secrets in a secured location not being tracked in git. Which is why we have a .env (git untracked, to be created from .env.example) file at the root location of this boilerplate which makes all the secrets present in it available in our project using python-dotenv package. Hence, it is super easy to get started with just creating .env from .env.example by setting correct values 😁

  • Creating multiple configurations (dev, staging, prod)
    Another important point is to separate global configurations based on the environment such as dev, staging, prod. That's the reason we have config.py, a file containing configuration classes based on the environments that get loaded in the Flask app based on the current working environment (which can be set in .env).

# configuration classes (app/config.py)
class BaseConfig(object):
    ''' Base config class. '''
...

class Development(BaseConfig):
    ''' Development config. '''
...

class Staging(BaseConfig):
    ''' Staging config. '''
...

class Production(BaseConfig):
    ''' Production config '''
...
config = {
    'development': Development,
    'staging': Staging,
    'production': Production,
}
# app/__init__.py
from config import config
...

def create_app():
    # loading env vars from .env file
    load_dotenv()
    APPLICATION_ENV = get_environment()
    app = Flask(APP_NAME)
    # loading config based on current environment
    app.config.from_object(config[APPLICATION_ENV])
    # APPLICATION_ENV is set in .env to set current environment

5. Logging

Logging is a means of tracking events that happen when some software runs. We add logging calls to our code to indicate that certain events have occurred.

As we already have environment based configurations in our app, all the other configurations can be changed according to the current app environment (dev, staging, prod). The loggers are configured in config.py and can be overridden in the respective app environments (dev, staging, prod) as shown below 👇

# config.py
class BaseConfig(object):
    ''' Base config class. '''
    ...
    LOG_INFO_FILE = path.join(basedir, 'log', 'info.log')
    LOG_CELERY_FILE = path.join(basedir, 'log', 'celery.log')
    LOGGING = {
        'version': 1,
    ...

6. Authentication

Authentication can be added authentication as per the need of the application. To start with, this boilerplate has API-Key based authentication whereas the API-Key is declared in .env file. In file, authentication.py, a decorator looks for x-api-key header in the request and returns 401 in case of mismatch. Once you have set up the app, you can test the test route (/core/restricted) for the same via curl with your API key set in .env.

$ curl --location --request GET 'http://localhost:5000/api/v1/core/restricted' --header 'x-api-key: <your-api-key>'

For more complex authentication logic, please write a custom middleware.

7. Database Setup

To keep the boilerplate as light as possible of the packages, it does not have any database package added. But worry not if you want to add one. 😌 It is pretty easy to configure the database as this boilerplate is created via the factory pattern method along with environment-based configuration as discussed in the above points. Let's take an example of one of the popular library - SQLAlchemy.

# app/__init__.py

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app(config_filename):
    ...
    app = Flask(__name__)
    app.config.from_object(config[APPLICATION_ENV])
    ...
    db.init_app(app)

The configuration would be read directly from app.config which we have loaded from config.py.

8. Task Queues (Celery) Setup

Task queues are used as a mechanism to distribute work across threads or machines. Commonly, even a small microservice would require to run tasks in the background (async). Hence, this boilerplate comes with a pre-loaded popular task queue Celery.

The configuration is quite simple as we are using Flask's factory pattern. We have also added a separate customized logger for celery logs which writes to a separate configurable file celery.log. Running celery is as easy as just running a command celery worker -A celery_worker.celery -l=info (from the root of our project).

9. Deployment

Flask Documentation provides a very good list of deployment options and the necessary steps as well. Also, you can refer to my earlier post on Deploying the Django application which is similar for flask application as well (with some minute changes). 😄

10. API Caching and Validation

We already covered a lot of points but two main aspects of having a robust microservice are to have an efficient validation and caching capability. This boilerplate comes with following two packages to achieve the same:

Redis A Python Redis client for caching
Webargs A Python library for parsing and validating HTTP request objects

🤓 Getting started with flask boilerplate

So far we talked about most of the points an ideal project requires that comes pre-loaded with this boilerplate. Whatever may be your use case for creating an app such as an email service, a communication service, reporting service, or even a microservice, you can use this boilerplate with minimal customization.

You can start using flask boilerplate using the following installation steps and kick start your project!

# clone the repo
$ git clone https://github.com/idris-rampurawala/flask-boilerplate.git
# move to the project folder
$ cd flask-boilerplate
# create pipenv environment for python 3
$ pipenv --three
# activate the pipenv environment
$ pipenv shell
# install all dependencies (include -d for installing dev dependencies)
$ pipenv install -d
# create `.env` from `.env.example` and set appropriate environment variables
# run the project
$ python run.py
# optionally check if everything is working correctly by calling status apps (check Test section of readme)

All set! Flask boilerplate is up and running and waiting for you to write some cool stuff from it 🥳


⭐ Useful Links


If you find this helpful or have any suggestions, feel free to comment. Also, do not forget to hit ❤️ or 🦄 if you like my post.

See ya! until my next post 😋

Posted on by:

idrisrampurawala profile

Idris Rampurawala

@idrisrampurawala

A Full Stack Developer specializes in Python (Django, Flask) & JavaScript technologies (Angular, Node.js). Experience designing, planning, building complete web applications with backend API systems

Discussion

markdown guide
 

This is a great post, I've had trouble in the past learning things mentioned here to extend Flask apps because there wasn't much out there that I could find which collated all this information, and how it fit into the bigger picture; not only did you do this, but you explained how and why you might need these. I learned a lot from this!

 

Thanks Jesse. That is exactly what I wanted to convey from this post. Glad you read it and liked it! 😊