Note: if you have an existing Flask application that you'd like to run on AWS Lambda, you can skip all the sections on building Flask applications and go straight to the chapter on using Zappa.

Flask is a wonderful Python web 'microframework' that allows you to easily build web app backends. They call it a microframework because it has a small core of functionality out of the box, but it has many extensions that are easily imported to provide a huge range of functionality.

I should say straight away that if you want to skip this section in favor of the official Flask tutorial, or use them in conjunction with each other to gain a deeper understanding, that's an option.

Without adding any extensions, flask has support for building HTTP endpoints, templating (via Jinja2) and secure cookies. I'll describe these in more detail when I go over application implementation. It's also got really great debugging and unit testing capabilities which I'll let you explore on your own.

The only extension I'll be using for the purposes of this guide will be Flask-Login, which provides user session management for Flask, but as we go along I'll mention more extensions that can make your life easier down the road.

Basic Application Structure

The core element of a Flask app is the application instance. You instantiate one like this:

from flask import Flask
app = Flask(__name__)

This application instance receives all HTTP requests from the web server through a calling convention called Web Server Gateway Interface (WSGI). Flask uses a library called Werkzeug to interface with the WSGI server — Werkzeug is a powerful WSGI utility library that we won't use directly very much.

To be very clear, Flask itself doesn't include a production web server; Werkzeug includes a dev server that can be used locally to test your application, but you'll want to use a real production server when your app is ready to deploy. In our case, we'll use Zappa, which wraps AWS API Gateway calls in order to support WSGI web apps (as well as other Python applications).

Side note: the variable __name__ that is passed to the Flask constructor is a special built-in Python attribute that returns the name of the current module. This is used by Flask to determine the relative locations of all the application files, like images and templates.

This is a fully functioning Flask web app:

from flask import Flask
app = Flask(__name__)
def hello():
    return 'Hello, World!'

As you can see, Flask contains decorators that define application routes. A route is a link between a URL and the function that handles it. As you may have guessed, the route decorator is what tells Flask where to route the HTTP request sent to the denoted URL. For the purposes of the app described in this guide the functions associated with specific routes will all be view functions — functions that return html pages to be displayed on the client side.

The final thing I want to mention in this brief introduction is how to run the tiny application above locally. All you need to do is save the code above in a file called profile_app.py, set two environment variables, and then invoke the flask dev server from the command line. In Linux this looks like:

export FLASK_APP=profile-app
export FLASK_ENV=development
flask run

Setting the FLASK_ENV variable to 'development' will save you a lot of time, because it will tell Flask to restart the local server whenever any file changes occur.

One final tip here: you can put the environment variable export lines at the end of your venv/bin/activate file and they will be imported every time you run source venv/bin/activate. That will allow you to skip exporting variables manually every time you start your flask test server.

Our Profile App

Now that we've got the basics of Flask down, let's start building! Since our application is small, we'll build it using a single monolithic Python file. This means that we won't go over Flask features that make it easy to break up a large application into separate files, like the blueprint feature. If you're planning on building a large Flask application please read up on application factories as well, which can make your code more maintainable in many ways. Speaking generally, the right design for you will depend on a number of things, including the number of people working on your codebase and the complexity of your application. For our specific application, a one-file design is fine.

form submission page

Let's start with a useful function, a form submission:

from flask import Flask, render_template, request

# app init
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        name = request.form['author_name']
        bio = request.form['author_bio']
        # here name and bio are printed so you can see that they
        # have been delivered, but nothing else is done with them yet.
        # To see the production version of the function see the source code
        # section at the end of the book.
        print(name, bio)

    return render_template("index.html")

We've added three new things to our primary view function:

  1. An additional parameter ('methods') to the route decorator,
  2. A call to the 'request' object from flask, and
  3. A call to the 'render_template' function that invokes the Jinja2 templating system.

The 'methods' parameter tells Flask what types of HTTP requests to accept. Here we're allowing the function to accept both GET and POST requests. The GET request will be used to present the form page, and the POST will be invoked if form data has been sent.

The 'request' object allows you to access the data in the current HTTP request. As you can see above, the form data is accessible in the form of a dictionary. The elements we're referencing ('author_name' and 'author_bio') are the name labels on the form input tags in our html index file. For an example form see the edit-page.html source code in our github repo. Be aware that this html file contains lots of other stuff too, mostly related to uploading author profile pictures and rich text biographies.

Finally, 'render_template' is a fantastically powerful function. It takes as arguments the name of an html template and keyword arguments of variables to insert into the template. In this context, template means a Jinja2 template. We have not yet given any keyword arguments, but we will soon. In its most simplistic form, a Jinja2 template is an html file that contains instructions on where to dynamically insert string variables. These instructions are given in the form of double curly braces, like this:

{{ string_var }}

Here string_var can be passed to the render_template function as a keyword argument and Jinja2 will add it between the paragraph tags before returning the rendered page. If string_var = "Check out this paragraph", the rendered template would look like:

Check out this paragraph

'render_template' will look for templates in the 'templates' folder in your flask project (to see project file structure, check out the github repo). And just to be clear, we don't need to use 'render_template' in the case of our index function above, since we're not passing in any keyword arguments — this just leaves us in a good place for when we enhance our index page.

That's it! As long as you have an 'index.html' with an appropriate form in your templates directory, you can run the function above. You've parsed your first HTTP request with Flask!

If you're finding this guide useful, you may want to sign up to receive more of my writing at cloudconsultant.dev.