Flask API Template


I often use Flask when I am writing a small web interface for an application. Flask proved to be a very simple easy to use framework and so I decided I would write an API. The end goal is to have a database back-end and a simple interface for retrieving and storing notes. However, this post is just about setting up a template and authentication for the API. If all you want is the code, there is a link to download a zip file at the bottom of the page.


Step 1 - Setting up the environment

First of all you are going to need a virtual environment. These are incredibly useful and super simple to setup. This can be done using Python like so

python3 -m venv venv

This will create a directory called venv in your current directory. Once this has done we can activate the virtual environment and install all the required libraries. If you haven’t yet downloaded the requirements file then you can do so here: requirements.txt.

source venv/bin/activate    # Linux
.\venv\bin\activate.bat      # Window

pip3 install -r requirements.txt

Now just wait and let pip do its thing and we are ready to start our application.


Step 2 - Creating the application structure

The main entry point for our application, at least for development, will be manage.py. This script allows us to run our server as well as run database functions such as initialisation and migrations.

#!/usr/bin/env python3
from flask_script import Manager
from flask_migrate import Migrate
from app import db, create_app

# Create the app
app = create_app("development")
migrate = Migrate(app, db)
manager = Manager(app)

manager.add_command("db", MigrateCommand)

if __name__ == "__main__":
    manager.run()

To run our application we can call this command: python manager.py runserver. When we run this command we will get lots of errors because we haven’t got an app module yet. So the first thing to do here is create the app folder. The general structure should look like this:

FlaskAPITemplate
 app
    auth
       __init__.py
       models.py
    config.py
    __init__.py
 manager.py
 requirements.txt

To start with just create the files and we can populate them shortly.


Step 3 - Creating the configuration

We will start with the config file. This defines how our application will run and various settings the app requires to run correctly. Our app will have 2 main configuration objects, one for development and the other for production.

import os
basedir = os.path.abspath(os.path.dirname(__file__))


class Config(object):
    """ This is the default config object that all other configs will inherit from """
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    CSRF_ENABLED = True
    SECRET_KEY = "somebiglongsecretkey"
    SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "database.db")


class Development(Config):
    """ Development specific configuration """
    DEBUG = True
    SQLALCHEMY_ECHO = True


class Production(Config):
    """ Production specific configuration """
    DEBUG = False


app_config = {
    "production": Production,
    "development": Development
}

The objects are fairly self explanatory in what they are. The dictionary at the bottom is what will define a configuration. It allows us to convert a string into a configuration object by referencing it like so: app_config["development"].


Step 4 - Creating the flask application

Most of the code to create the application lives inside the __init__.py. We use an application factory (as described here). This means that all of the app creation is done in one function that.

from flask import g, request, jsonify, make_response
from flask_api import FlaskAPI
from flask_sqlalchemy import SQLAlchemy

# Setup the database
db = SQLAlchemy()

from app.config import app_config

def create_app(conf):
    # Create the app
    app = FlaskAPI(__name__, instance_relative_config=True)
    # Apply our config
    app.config.from_object(app_config[conf])
    # Connect it to the database
    db.init_app(app)

    # Add a default route
    @app.route("/")
    def index():
        return make_response(
            jsonify({"status": "success", "message": "Server active"}), 
        200)

    return app

Some lines to note here. Lines 11-13 are how the app is actually created. Once the API is created, the configuration object is applied to it (line 12). After the configuration has been parsed, without errors, we finish setting up SQLAlchemy. This is what allows us to connect to the SQLite backend.

The SQLAlchemy Object Relational Mapper presents a method of associating user-defined Python classes with database tables, and instances of those classes (objects) with rows in their corresponding tables.

SQLAlcehmy Docs

On line 15 we define what is known as a route, for our API. This means that when you visit “/”, of the API you will get the response defined inside the index function. If you visit the server URL in your browser you will see something akin to figure 1. Now that we have a function to create our app, we are able to run our server. To do this we need to run the file with the runserver parameter.

python3 manage.py runserver

Step 5 - Authorization for our API

Currently our API has no form of authorization. This means that anyone who knows where are API is located can use it, not ideal. All of the authorization will be done using the Flask module HTTPAuth, specifically the HTTPBasicAuth class. To keep our app modular, we will create a sub-module inside our app called auth. This means that it is easy to include to it in our app and just as easy to remove it. The meat of the auth module will be inside the __init__.py file (inside the auth folder). This file will contain the routes and logic of the module. The models file contains description of the required database tables.

First off, we will need our user model. This will describe the table that all the usernames and password are stored in. The models file will look like this the following. Feel free to add more fields to your model such as first name, last name etc.

from app import db
from werkzeug.security import generate_password_hash, check_password_hash


class User(db.Model):
    """ Defines the users table """

    __tablename__ = "users"
    # These are the table columns
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, nullable=False, index=True)
    password = db.Column(db.String(255))

    def __init__(self, user, password):
		""" Create a new user. Takes a plaintext password and hashes it """
        self.username = user
        self.hashPassword(password)

    def hashPassword(self, plaintext):
        """ Hashes the plaintext password """
        self.password = generate_password_hash(plaintext)

    def checkPassword(self, plaintext):
		""" Returns true if the given password is correct """
        return check_password_hash(self.password, plaintext)

    def __repr__(self):
        return "<Users: {}>".format(self.username)

An important thing to note here is that we are hashing our passwords. That means that, if for some reason, someone gets access to our database, they cannot see all our passwords. Now we have a model, we can create our API routes. The routes that we are going to add are

  • /users/add
  • /users/remove
  • /users/get/
from flask import make_response, jsonify, request
from app import authorization
from app.auth.models import User

def create_module(app):
    @app.route('/users/add', methods=['POST'])
    @authorization.login_required
    def adduser():
        username = request.data.get('username')
        password = request.data.get('password')
        # Ensure both a username and password were given
        if username is None or password is None:
            return make_response(jsonify({'status': 'fail', 'message': 'Malformed request'}), 400)
        if Users.query.filter_by(username=username).first() is not None:
            return make_response(jsonify({'status': 'fail', 'message': 'User already exists'}), 400)
        # Create a new user object and add them to our database
        user = User(username, password)
        try:
            db.session.add(user)
            db.session.commit()
        except Exception as e:
            return make_response(jsonify({'status': 'fail', 'message': 'An unkown error occured'}), 400)

        return make_response(jsonify({'status': 'success', 'message': 'Added user'}), 200)

    @app.route("/users/remove", methods=["POST"])
    @authorization.login_required
    def removeuser():
        username = request.data.get("username")
        if username is None:
            return make_response(jsonify({'status': 'fail', 'message': 'Malformed request'}), 400)
        user = User.query.filter_by(username=username).first()
        if user is not None:
            try:
                db.session.delete(user)
                db.session.commit()
            except Exception as e:
                return make_response(jsonify({'status': 'fail', 'message': 'Failed removing user'}), 400)
            return make_response(jsonify({'status': 'success', 'message': 'User removed'}), 200)
        else:
            return make_response(jsonify({'status': 'fail', 'message': 'User does not exist'}), 200)

    @app.route('/users/get/<string:user>', methods=['GET'])
    @authorization.login_required
    def getuser(user):
        if user is None:
            return make_response(
                jsonify({'status': 'failed', 'message': 'Invalid request'}),
                400
            )
        userObj = User.query.filter_by(username=user).first()
        if userObj is not None:
            return make_response(
                jsonify({'status': 'success','message': 'User found','username': user}),
                200
            )
        else:
            return make_response(
                jsonify({'status': 'fail','message': 'User does not exist'}),
                200
            )

Our authorization module is now complete. We just need to integrate it into our app.


Step 6 - Integrating the auth module

First up we need to edit the app/__init__.py file.

  1. Underneath line 8 we need to import our user model so that we can query the user database when someone attempts to login
from app.auth.models import User
  1. After we initialise our database on line 13 we need to create the auth function that validates user credential. This function will log the user in and add their User object to the global session.
@authorization.verify_password
def verify_password(username, password):
    user = User.query.filter_by(username=username).first()
    if not user or not user.checkPassword(password):
        return False
    g.user = user
    return True

After our API is set up we need to create the database and our first user. To do this I add in a custom command to the manager file that we have. The whole file should look like this:

from flask_script import Manager, Command
from flask_migrate import Migrate, MigrateCommand
from app import db, create_app
from app.auth import create_module as auth_create_module

app = create_app("development")
auth_create_module(app)

class Setup(Command):
    """ Adds a default user to the database """

    def run(self):
        from app.auth.models import User
        user = User("admin", "admin")
        db.session.add(user)
        db.session.commit()


migrate = Migrate(app, db)
manager = Manager(app)

manager.add_command("db", MigrateCommand)
manager.add_command("setup", Setup())

if __name__ == "__main__":
    manager.run()

To set up the database we need to run several commands

python manager.py db init
python manager.py db migrate
python manager.py db upgrade

The entire API is now ready to go. Adding in more modules is easy and takes the same for as adding in the auth module. Create a new directory for the module. Create a models file for any database tables ou require. Add routes and functions to the __init__ file. Import the create_module function and that it. You can test the API with the following cURL command:

curl -X GET -u admin:admin -d "username=admin" http://127.0.0.1:5000/users/get

Here is a link to the complete code archive: FlaskAPITemplate.zip