In this post we’ll set up a Flask app serving as a starting point for building a REST API. If you know some Python, you will be able to go through the steps below to successfully set up the API on your own. Note: I’m using Python 3.6.3.

The full source code can be found on Github: https://github.com/visini/barebone-flask-rest-api

What we’ll use:

  • Python 3 and PyCharm as an IDE
  • AWS Elastic Beanstalk to host our API
  • Flask, a popular Python micro framework for the web
  • PostgreSQL as a database (hosted remotely on AWS RDS)
  • Let’s Encrypt for SSL encryption
  • Gunicorn (WSGI server)

The end result is a fully working REST API serving as a starting point for your project (for example, a microservice).


Step 1: Set up project folder and install Flask

In this step we will install all requirements on our local machine. I’m using macOS 10.12, so the commands that follow might not correspond exactly to your environment. Run the following in Terminal:

# create and go to project folder
cd ~/PycharmProjects/ # cd into your main project folder
mkdir barebone-flask-rest-api
cd barebone-flask-rest-api
# create virtual environment and activate it
python3 -m venv my_virtual_environment
source my_virtual_environment/bin/activate
# within virtual environment, install the following:
pip3 install flask flask-restplus gunicorn psycopg2 Flask-SQLAlchemy
pip3 freeze > requirements.txt
deactivate
# set up file structure
touch app.py wsgi.py database.py models.py

Important: Be sure while debugging, when serving the API or when you install additional dependencies below using pip3, change to the virtual environment with the following commands:

source my_virtual_environment/bin/activate # activate virtual env
# your commands here
pip3 freeze > requirements.txt # after you're done, this will ensure that requirements.txt is up to date
deactivate # this will exit the virtual environment

Open the project folder in PyCharm with the following command (or, use the text editor of your choice). You might need to create the command-line launcher in advance from PyCharm > Tools (see here).

charm .

To get started, add the following barebone code to app.py:

# app.py
from flask import Flask
from flask_restplus import Resource, Api

application = Flask(__name__)
api = Api(application,
          version='0.1',
          title='Our sample API',
          description='This is our sample API',
)

@api.route('/hello')
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

if __name__ == '__main__':
    application.run(debug=True)

In wsgi.py paste the following to prepare the WSGI server (i.e. gunicorn):

# wsgi.py
from app import application

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

You can test if everything is working properly in two ways:

# First option: Run with python interpreter (only for debugging)
python3 app.py --debug
# * Running on http://127.0.0.1:5000/
# * Restarting with stat
# * Debugger is active!
# Second option: Run with gunicorn (used for production later)
gunicorn --bind 0.0.0.0:8080 wsgi:application -w 1
# Starting gunicorn 19.7.1
# Listening at: http://0.0.0.0:8080 (19694)
# Using worker: sync
# Booting worker with pid: 19697

In Terminal, you can press CTRL+C to exit the process accordingly. By default, flask-restplus provides Swagger UI documentation, served from the root URL of the API. After accessing the root URL (see above, for example http://0.0.0.0:8080/) from your browser, you’ll be presented with the automatically-generated Swagger UI documentation, in which you even can test the API using the “Try it out!” button:

Try calling the API endpoint as following from the console (Note: Open a new window in Terminal, in order not to interrupt the current process):

curl http://0.0.0.0:8080/hello

You can see that upon a GET request, our endpoint /hello will output the expected response from app.py, conveniently formatted in JSON:

{"hello": "world"}

Our API is now working with a minimal “hello world” scenario and we are ready for Step 2, where we will implement another endpoint which will communicate with our PostgreSQL database.


Step 2: Database integration

Normally, an API needs to perform operations on a database (i.e. create, read, update and delete, see CRUD). That’s why we will connect our API to a database, in our case a remote PostgreSQL database hosted on AWS RDS.

In our PostgreSQL database, we’ll create a simple table called blog_posts for our purposes by running the following SQL code:

CREATE TABLE blog_posts (
   id serial primary key,
   title text,
   post text
);
INSERT INTO blog_posts (title, post) VALUES ('A title', 'Lorem ipsum dolor');
INSERT INTO blog_posts (title, post) VALUES ('Another title', 'Ipsum dolor sit amet');

First, we will set up database.py:

# database.py
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker

engine = create_engine('postgres://your_username:your_password@your_rds_subdomain.rds.amazonaws.com/your_database', convert_unicode=True)
metadata = MetaData()
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))

def init_db():
    metadata.create_all(bind=engine)

Then, we’ll set up models.py to reflect our database schema:

# models.py
from sqlalchemy import Table, Column, Integer, Text
from sqlalchemy.orm import mapper
from database import metadata, db_session

class BlogPost(object):
    query = db_session.query_property()
    def __init__(self, id=None, title=None, post=None):
        self.id = id
        self.title = title
        self.post = post

blog_posts = Table('blog_posts', metadata,
    Column('id', Integer, primary_key=True),
    Column('title', Text),
    Column('post', Text)
)

mapper(BlogPost, blog_posts)

In app.py, we need to add new import statements and an additional method, which will retrieve all blog posts from the table. Change the code accordingly:

# app.py
from flask import Flask
from flask_restplus import Resource, Api, fields
from database import db_session
from models import BlogPost

application = Flask(__name__)
api = Api(application,
          version='0.1',
          title='Our sample API',
          description='This is our sample API',
)

@api.route('/hello')
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

@api.route('/blog_posts')
class BlogPosts(Resource):
    model = api.model('Model', {
        'id': fields.Integer,
        'title': fields.String,
        'post': fields.String,
    })
    @api.marshal_with(model, envelope='resource')
    def get(self, **kwargs):
        return BlogPost.query.all()

@application.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()

if __name__ == '__main__':
    application.run(debug=True)

Now, serve the API as before by typing the following command in the console, for example with gunicorn:

gunicorn --bind 0.0.0.0:8080 wsgi:application -w 1

Try curling the endpoint we just created from a new Terminal window:

curl http://0.0.0.0:8080/blog_posts

You can see that upon a GET request, our endpoint /blog_posts will query the database and return the blog posts conveniently formatted in JSON:

{
 "resource": [{
  "id": 1,
  "title": "A title",
  "post": "Lorem ipsum dolor"
 }, {
  "id": 2,
  "title": "Another title",
  "post": "Ipsum dolor sit amet"
 }]
}

We successfully connected to our database and are ready for Step 3, where we will prepare our app for deployment to AWS Elastic Beanstalk.


Step 3: Prepare for deployment

In order to deploy our API to AWS Elastic Beanstalk, we first need to install the AWS Elastic Beanstalk command line interface, called awsebcli. After some trial and error when trying to install it with pip3, I managed to install it successfully using brew, which seems to handle the dependencies correctly. If you have it already installed on your machine, you can skip this step. Run this command in Terminal to install the CLI:

brew install awsebcli

You can check if the installation was successful when you can execute the following without any errors:

eb --version

Now, run the following command in your project folder to initialize the application and register it with AWS Elastic Beanstalk (be sure to change the region accordingly; below, eu-west-1 is selected):

eb init -p python-3.4 -r eu-west-1 barebone-flask-rest-api

Note: Python 3.4 is the latest version available as of the time of writing.

This will trigger prompts which will help you to set up the application. You will need to enter your AWS credentials, namely aws-access-id and aws-secret-key.

In order to install PostgreSQL on the EC2 instances where our API will be deployed, an additional step is required. Paste the following in Terminal to add additional dependency installation instructions to the corresponding file to enable PostgreSQL support in AWS Elastic Beanstalk environments (the file is located at .ebextensions/packages.config):

mkdir .ebextensions
echo "packages:
  yum:
    postgresql94-devel: []" >> .ebextensions/packages.config

The following command will create an environment (called testenv-barebone-flask-rest-api), provision the necessary instance (we’ll use a single instance setup without load balancer for now) and deploy your application:

eb create testenv-barebone-flask-rest-api --single -i t2.nano
eb use testenv-barebone-flask-rest-api

This will take a few minutes to complete. Note that at this point, the application will not work yet and throw an Internal Server Error. This is because the default WSGI-Path is application.py whereas in our project it is wsgi.py. Luckily, this issue can be fixed quite easily.

In order to tell AWS Elastic Beanstalk to set up the EC2 instances with the correct WSGI-Path (where our API is ultimately called from), we have to perform the following steps. Run this in Terminal:

eb config

Then, scroll down to this section and in the last line, change application.py to wsgi.py. This will ensure that the correct file from our project folder is referenced (in other words, wsgi.py is the entry point to our application).

[...]
aws:elasticbeanstalk:container:python:
   NumProcesses: '1'
   NumThreads: '15'
   StaticFiles: /static/=static/
   WSGIPath: wsgi.py # change this from application.py to wsgi.py
[...]

Exit using CTRL-C and confirm with Yes. This will subsequently deploy the new configuration to your instances (in our case, still a single instance).

After deployment of the new configuration (which takes another 2–3 minutes) you can type the following command to open the URL under which your API is served from to confirm that everything is working properly:

eb open

You can also retrieve the URL (along other useful information about your environment) like this:

eb status

For example:

testenv-barebone-flask-rest-api.abcde.eu-west-1.elasticbeanstalk.com

You can see that the two methods we’ve set up are working properly by calling them from your browser (like we’ve done before, for example by navigating to the endpoint /hello). Alternatively, you can test your endpoints using Postman or Paw – especially when implementing more methods it will come handy and speed up your debugging.

If you make changes to the source code of your API, you can re-deploy it to AWS Elastic Beanstalk via the following command:

eb deploy

Step 4: Configure SSL using Let’s Encrypt

The steps below will show you how to configure SSL for free, using Let’s Encrypt. First, you’ll need to create a hosted zone for your custom domain in Route 53. Then, you can attach a CNAME Alias in the hosted zone, pointing to your elastic beanstalk deployment. Please check out this AWS guide for more information.

Note: The next step will download a config file from gist.github.com. Be sure to check out the source code before pasting the command into your command line!

After you can access the custom domain, and are able to see the same output as when accessing it via the AWS Elastic Beanstalk subdomain, run the following commands in Terminal in the root of your project (be sure to replace the two environment variables with your email, as well as your domain name configured in Route 53 from the step above):

eb setenv LETSENCRYPT_EMAIL=your@email.com LETSENCRYPT_DOMAIN=your_custom_route53_domain_alias.com
mkdir .ebextensions
wget -q https://gist.githubusercontent.com/visini/70c5b11c136be16ea2fe12a6d7b9bf3b/raw/231d3dedf4256b9081e46421714d7f8477055def/elasticbeanstalk-letsencrypt-ssl.config -O .ebextensions/elasticbeanstalk-letsencrypt-ssl.config
eb deploy

The config script put in place in the step above will install a cronjob to make sure that your SSL certificates will automatically renew every week. Now, your API is accessible on your custom domain via AWS Elastic Beanstalk, secured via SSL provided by Let’s Encrypt. You can check if everything is working properly, if the certificate of your API is valid and trusted, and if you can navigate to both the index page and to the contact page.

I hope you enjoyed this guide  -  please let me know if you have any questions or if you run into any issue. Thanks for reading!