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 (seeapp/config.py
). This function will attempt to load the correct mode using either the passed-inconfig
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.