Setup a simple hello world Flask API App

Intro

In this tutorial, I will create a basic hello-world Flask application using the file structure from the previous tutorial. This application will simply create an api endpoint at api/helloworld/helloworld and return a message helloworld in json format.

Source code

You can download the source code on gibhub.

Setup the environment and install dependencies

Lets create the application folder first. For the purposes of this tutorial, I'll refer to our application as DoubleDibz.

$ cd [to where you want your application folder to be]
$ mkdir DoubleDibz

Before creating any more folders and files, we would need to setup the environment. Here I am assuming you guys have python, pip, and virtualenv installed already. If not, take a look here. It is generally a best practice to not install dependencies in the Python global packages, and instead install them in an isolated environment for each of your applications.

To create the virtual environment for DoubleDibz, do the following:

$ cd DoubleDibz
$ virtualenv .pyenv
New python executable in .pyenv/bin/python  
Installing setuptools, pip...done.  

Now the virtual environment resides in the .pyenv/ folder. Lets activate the environment!

$ source .pyenv/bin/activate

At this point, the name of the virtual environment (.pyenv) should appear on the left of your command prompt:

(.pyenv)UserName@Your-Computer:Some-folder/DoubleDibz $

From now ons, any package you install via pip will be installed locally in the .pyenv folder, separate from the global dependencies. Okay, lets go ahead and install what we need for this application.

$ pip install Flask
$ pip freeze > requirements.txt
$ cat requirements.txt
Flask==0.10.1  
Jinja2==2.8  
MarkupSafe==0.23  
Werkzeug==0.10.4  
itsdangerous==0.24  
wsgiref==0.1.2  

Now we have installed all the necessary dependencies for our application. You can see a list of all the packages and versions installed in the current environment with the command pip freeze. Notice how we save the output to requirements.txt. This allows you or other developers to recreate this same environment to ensure consistency in development and deployment (install dependencies on actual servers).

$ pip install -r requirements.txt

Remember to exclude the virtualenv (.pyenv or however you named it) from your version control by adding to the ignore list (.gitignore if youre using git).

Step 1: Create the file layout

mkdir app  
mkdir app/templates  
mkdir app/static  
mkdir app/frontend  
mkdir app/common  
touch run.py  
touch app/__init__.py  
touch app/config.py  
touch app/app.py  
touch app/extensions.py  
touch app/api/helloworld.py  
touch app/api/__init__.py  
touch app/common/constants.py  
touch app/common/__init__.py  

Our current structure:

DoubleDibz  
├── app
│   ├── __init__.py
│   ├── api 
│   │   ├── __init__.py
│   │   └── helloworld.py
│   ├── app.py
│   ├── common
│   │   ├── __init__.py
│   │   └── constants.py
│   ├── config.py
│   ├── extensions.py
│   ├── static
│   └── templates
└── run.py

Step 2: Edit app/app.py

Let's start first with the core of the application.

$ vim app/app.py
import os  
from flask import Flask

import config as Config  
from .common import constants as COMMON_CONSTANTS  
from .api import helloworld

# For import *
__all__ = ['create_app']

DEFAULT_BLUEPRINTS = [  
   helloworld
]

def create_app(config=None, app_name=None, blueprints=None):  
   """Create a Flask app."""

   if app_name is None:
     app_name = Config.DefaultConfig.PROJECT
   if blueprints is None:
     blueprints = DEFAULT_BLUEPRINTS

   app = Flask(app_name, instance_path=COMMON_CONSTANTS.INSTANCE_FOLDER_PATH, instance_relative_config=True)
   configure_app(app, config)
   configure_hook(app)
   configure_blueprints(app, blueprints)
   configure_extensions(app)
   configure_logging(app)
   configure_error_handlers(app)
   return app

def configure_app(app, config=None):  
   """Different ways of configurations."""

   # http://flask.pocoo.org/docs/api/#configuration
   app.config.from_object(Config.DefaultConfig)

   if config:
     app.config.from_object(config)
     return

   # get mode from os environment
   application_mode = os.getenv('APPLICATION_MODE', 'LOCAL')
   app.config.from_object(Config.get_config(application_mode))

def configure_extensions(app):  
   pass

def configure_blueprints(app, blueprints):  
   for blueprint in blueprints:
      app.register_blueprint(blueprint)

def configure_logging(app):  
    pass

def configure_hook(app):  
   @app.before_request
   def before_request():
      pass

def configure_error_handlers(app):  
   # example
   @app.errorhandler(500)
   def server_error_page(error):
      return "ERROR PAGE!"

app/app.py exposes the function create_app which sets up the Flask application with the desired configuration. The file abstracts out the process of configuring the Flask app in multiple stages:

  • configure_app : initialize the Flask application with the correct configuration. I setup my application to have 3 modes of configs: local, staging, and producation (see app/config.py). This function will attempt to load the correct mode using either the passed-in config object or by identifying the mode from the environment variable (This is the most flexible for configuring staging and production servers).
  • configure_blueprints: register the given list of Blueprint modules to the application. A common design pattern in Flask is factoring the application into multiple Blueprints modules to greatly simplify the complexity.
  • configure_extensions: initializes Flask extensions for the application. We will see later how we use this function to initialize Flask-WTF, Flask-Script and Flask-SQLAlchemy.
  • configure_logging : configure the way we want to log data. Depending on the environment, we would like to take different approach. For example, when testing locally, you might simply want to log data to stdout console, but for production servers you want to save in some log files.
  • configure_hook: this is a bit less specific. In general, I use it to process requests and responses. For example, I could use it log every incoming requests or measure the time it takes for the server to generate a response for every request.
def configure_hook(app):  
   // log every incoming requests
   @app.before_request
      def before_request():
         app.logger.debug('Hitting %s' % request.url)
  • configure_error_handlers: Sets up how we want to handle errors. For our purposes, we would return errors in API json format.

Step 3: Edit app/config.py

$ vim app/config.py
import os  
from common.constants import INSTANCE_FOLDER_PATH

class BaseConfig(object):

   PROJECT = "app"

   # Get app root path, also can use flask.root_path.
   PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))

   DEBUG = False
   TESTING = False

   ADMINS = ['youremail@yourdomain.com']

   # http://flask.pocoo.org/docs/quickstart/#sessions
   SECRET_KEY = 'secret key'

class DefaultConfig(BaseConfig):

   # Statement for enabling the development environment
   DEBUG = True

   # Secret key for signing cookies
   SECRET_KEY = 'development key'

class LocalConfig(DefaultConfig):  
   # config for local development
   pass

class StagingConfig(DefaultConfig):  
    # config for staging environment
    pass

class ProdConfig(DefaultConfig):  
    # config for production environment
    pass

def get_config(MODE):  
   SWITCH = {
      'LOCAL'     : LocalConfig,
      'STAGING'   : StagingConfig,
      'PRODUCTION': ProdConfig
   }
   return SWITCH[MODE]

The goal here is to have enough flexibility for different environment. As you can see, we have three configuration mode: LocalConfig, StagingConfig, and ProdConfig. All of which inherit from DefaultConfig which defines a set of standard settings such as project root directory and project name.

Step 4: Setup the helloworld endpoint

Now you have setup all the basics and the last thing you have to do is creating the API endpoint.

$ vim app/api/helloworld.py
"""
 Simple API endpoint for returning helloworld
"""
from flask import (Blueprint, render_template, current_app, request,  
                   flash, url_for, redirect, session, abort, jsonify, make_response)

helloworld = Blueprint('helloworld', __name__, url_prefix='/api/helloworld')

@helloworld.route('/helloworld', methods=['GET'])
def index():

   data = {
      'message' : "helloworld"
   }      
   return make_response(jsonify(data))

Step 5: Create __init__ files

For each folder we have, we would need to create a __init__.py file to mark the directory as a Python package. __init__.py is usually empty, but it can be used to expose selected portion of the package or rename them to more convenient/more specific names.

$ vim app/__init__.py
from app import create_app  
$ vim app/api/__init__.py
from .helloworld import helloworld  

This creates a api package that exposes the different API blueprints that we have without revealing the individual files and the implementation detail. This allows app.py to import the helloworld blueprint from the api/ directory level.

// in app.py
from .api import helloworld

// instead of loading from the specific api file
from .api.helloworld import helloworld  

Step 6: Edit constants.py

The app/common/ folder stores constants, functions and objects that are common for every modules we create (hence the name common). For now, we will only need a constants.py file.

$ vim app/common/constants.py

Place the following content that defines the directory path for the instance folder.

import os

# Instance folder path, make it independent.
INSTANCE_FOLDER_PATH = os.path.join('/tmp', 'instance')  

Step 7: Run and see!

When running the server locally, we'll use run.py

$ vim run.py

Place the contents:

import os  
from app import create_app

app = create_app()

if __name__ == '__main__':  
   port = int(os.environ.get("PORT", 5000))
   app.run(host='0.0.0.0', port=port, debug=True)

Running the file wil create a Flask server running at local port 5000.

$ python run.py
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
* Restarting with stat

Now you can hit your browser url : http://localhost:5000/api/helloworld/helloworld to see if the API endpoint is working properly. Alternatively, you can test with curl:

$ curl http://localhost:5000/api/helloworld/helloworld
{
  "message": "helloworld"
}

Yay, it works! Good job. Now you have successfully setup a basic Flask application with appropriate structure. Now lets get more advanced in the next section where we'll create a user authentication in Flask.