Develop a Serverless Flask REST API with Zeit Now

Deploy a Flask-powered Serverless JSON REST API to Zeit Now, a platform for global serverless deployments

This post explains how to set up a Serverless Flask app serving as a starting point for building a REST API.

Project source code available at...
visini/barebone-serverless-flask-api-zeit-now

We’ll use…

  • Python 3 and PyCharm as an IDE
  • Zeit Now, a serverless platform
  • Flask, a popular Python micro framework for the web
  • Flask-RESTPlus, a Flask extension for building REST APIs
  • PostgreSQL as a database (hosted remotely on AWS RDS)
  • Psycopg2, the most popular PostgreSQL adapter for Python

The result is a Serverless Flask-powered REST API serving as a starting point for your project (for example, a microservice). As it is hosted on Zeit Now, you’ll be able to deploy it without having to provision any servers.

Preliminary Steps

Make sure that you have signed up for a Zeit account and have installed the command line interface, Now CLI. In order to do that, and automatically set up your local environment with the correct credentials to deploy to Zeit Now, you can download and install the macOS client, Zeit Desktop. You’ll have to sign in via the app from the menu bar. Now Desktop also installs Now CLI (the command line interface for Now). We’ll use the Now CLI to deploy our API to Zeit Now.

Project Setup

In this step we will install all requirements on our local machine. I’m using macOS 10.12 and Python 3.6 - the commands might not correspond exactly to your environment, so be sure to watch out for that.

# setting up project folder
cd ~/PycharmProjects
mkdir barebone-serverless-flask-api-zeit-now
cd barebone-serverless-flask-api-zeit-now
 
# setting up virtual environment
python3 -m venv my_virtual_environment
source my_virtual_environment/bin/activate
pip3 install flask flask-restplus Flask-SQLAlchemy psycopg2
pip3 freeze > requirements.txt
deactivate
 
# setting up project structure and open project in PyCharm
touch now.json index.py
charm .

The first step is to configure the deployment settings for Zeit Now. Many thanks for the now-flask builder provided by @liudonghua123. Add the following configuration:

now.json
{
  "version": 2,
  "name": "barebone-serverless-api-zeit-now",
  "builds": [
    {
      "src": "*.py",
      "use": "@liudonghua123/now-flask",
      "config": { "maxLambdaSize": "30mb" }
    }
  ],
  "routes": [{ "src": "/.*", "dest": "/" }]
}

To begin with, we will set up a route /hello_world, which will output a basic „Hello World“ example:

index.py
from flask import Flask
from flask_restplus import Resource, Api, fields
from werkzeug.contrib.fixers import ProxyFix
 
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(app,
          version='0.1',
          title='Our sample API',
          description='This is our sample API'
)
 
@api.route('/hello_world')
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}
 
if __name__ == '__main__':
    app.run(debug=True)

Now, we can test if everything is working properly by running the following command in Terminal (alternatively, we can run it directly from within PyCharm with CTRL+R:

python3 index.py

The above command will run Flask in debug mode so you can develop locally. You’ll be able to check if everything is working by navigating to http://127.0.0.1:5000/hello_world and observing the response:

Hello World
Hello World

By accessing the root of your API, http://127.0.0.1:5000/, you will be presented with a documentation page describing the API (optionally, you can download the Swagger-specifications in json format – more on this later).

Sample API
Sample API

You can quit the server with CTRL + C. Deploying the app to Zeit Now requires only one single command. Just run the following command in Terminal while in your project folder:

now

That’s it! You can check if everything was correctly deployed to Zeit Now by navigating to the endpoint /hello_world of the deployment URL retrieved from Terminal (it is conveniently copied to your clipboard). It should return the identical output {"hello": "world"}, as we’ve already seen in the previous step from testing the API locally.

In the next steps, we will connect our API with a PostgreSQL database, set up an additional endpoint to retrieve data and finish our boilerplate.

Database Setup

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:

blog_posts.sql
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');

The next step is to download a custom compiled psycopg2 C-library for Python. Using psycopg2 via requirements.txt (conventional approach for installing modules) will not work. We will need to use the library from this GitHub repository. Be sure to download the repository to your machine, and (as we use Python 3) place the folder psycopg2-3.6 at the same level of your now.json file (root project folder). Then, rename the folder so it’s called psycopg2. As Zeit Now uses AWS Lambda to deploy your project, we need to use this custom pre-compiled library. Many thanks to Jeff Kehler.

The next step involves creating a database connector file, and setting up a separate route. Execute the following command:

touch database.py models.py

When testing our project locally, we need to use the regular psycopg2 module, but when deployed, we need to add instructions to load the custom pre-compiled module installed above (note the first try/except clause in the code below). In database.py, add the following code (be sure to modify your database credentials accordingly):

database.py
import importlib.machinery
from distutils.sysconfig import get_python_lib
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
 
# if local environment, use psycopg2 installed in python library (site-packages), otherwise use from binary (psycopg2 subdirectory in this folder)
try:
    psycopg2 = importlib.machinery.SourceFileLoader('psycopg2', get_python_lib()+'/psycopg2/__init__.py').load_module()
except:
    import psycopg2
 
username = "xxx"
databasename = "xxx"
host = "xxx.eu-west-1.rds.amazonaws.com"
password = "xxx"
 
engine = create_engine('postgres://{}:{}@{}/{}'.format(username, password, host, databasename), convert_unicode=True)
metadata = MetaData()
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
 
def init_db():
    metadata.create_all(bind=engine)

To reflect our database schema we will define a model for the blog posts. Add the following code:

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)

Now, we will have to set up the new route /blog_posts in our main file, index.py, by modifying its content to the following code (note the addition of the two import statements):

index.py
from flask import Flask
from flask_restplus import Resource, Api, fields
from werkzeug.contrib.fixers import ProxyFix
from database import db_session
from models import BlogPost
 
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(app,
          version='0.1',
          title='Our sample API',
          description='This is our sample API'
)
 
@api.route('/hello_world')
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}
 
@api.route('/blog_posts')
class BlogPosts(Resource):
    model = api.model('BlogPost', {
        'id': fields.Integer,
        'title': fields.String,
        'post': fields.String,
    })
    @api.marshal_with(model, envelope='resource')
    def get(self, **kwargs):
        return BlogPost.query.all()
 
@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()
 
if __name__ == '__main__':
    app.run(debug=True)

You can test if everything is working as expected by running the following command (or, again, directly run it from within PyCharm with CTRL+R:

python3 index.py

By navigating to http://127.0.0.1:5000/blog_posts, you can see that all entries are retrieved as expected from the database and returned in JSON format:

Blog Posts
Blog Posts

The only step remaining is to deploy the API to Zeit Now and expose it to the public so you can connect it to your frontend.

Deployment to Zeit Now

All that remains is to deploy our code to Zeit Now. Again, this is extremely convenient and can be done by running the following command again:

now

Accessing the endpoint /hello_world of the URL returned by the Now CLI will return the „Hello World“ example, and navigating to the endpoint /blog_posts will return the database entries in JSON format:

Blog Posts Zeit
Blog Posts Zeit

You can check the logs by accessing the endpoint /_logs of your deployment URL. This will redirect to the logs accessible on your Zeit account (logs are not public and can only be accessed by you when being logged in to your account).

Note: You can download the Swagger specifications for import to your favorite API Development Environment (for instance, Postman or Paw). This will allow you to test your API more efficiently, especially as soon as it starts to grow when you implement more routes and functionality. In Paw, you can import the swagger.json (located at the root of your project) with CTRL+ALT+⌘+SHIFT+I very easily, allowing you to just paste the URL of your swagger.json file. Be sure to install the import specifications for Swagger 2.0 before importing. After importing, everything is set up automatically:

Paw Mac App
Paw Mac App

You’ve successfully set up a serverless Flask-powered JSON API serving as a starting point for your project. You can add more endpoints / routes according to the specifications of your projects. P.S.: You can also clone the GitHub repository to get a head start for your next project, instead of following the steps above – it contains all the files from this tutorial, including the pre-compiled psycopg2 library.

Zeit Now is free up to a certain point – check out their pricing on how it scales as you grow.

Project source code available at...
visini/barebone-serverless-flask-api-zeit-now

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

© 2024 Camillo Visini
Imprint RSS