Share on facebook
Share on twitter
Share on linkedin

Create a Serverless Python API with AWS Amplify and Flask

Ali Spittel
Ali Spittel

Flask is one of my favorite tools for creating a quick API. Python is still my favorite programming language despite using JavaScript primarily the past few years — the syntax feels straightforward and elegant to me and the power of Python libraries like Pandas is incredible.

Deploying Python applications is not always the most fun activity, so we’re also going to use AWS Lambda to make our app serverless. In addition, we’ll use AWS Amplify’s command line interface to create and manage our resources.

We’ll also need a database to store our data, in this case we’ll build an API for Britney Spears’ songs and use DynamoDB store our data.

This tutorial will be most helpful for someone who already has basic knowledge of AWS Amplify, how APIs work, and Python.

Let’s dive into the code!

Project Setup

First, we’ll initialize an Amplify project. You’ll need to have the Amplify CLI installed and configured and Node installed. I’ll start with a React app, but we’ll focus on the backend for the tutorial so if you aren’t a React dev you should be fine!

Run the following commands in your terminal to create a React boilerplate, change into it, and then initialize Amplify.

$ npx create-react-app britney-spears-api
$ cd britney-spears-api
$ amplify init

Initializing Amplify will prompt you with some questions. For all of these, you can choose the default by just pressing enter!

? Enter a name for the project britneyspearsapi
? Enter a name for the environment dev
? choose your default editor: Visual Studio Code
? Choose the type of app that you\'re building javascript
? What javascript framework are you using react
? Source Directory Path: src
? Distribution Directory Path build
? Build Command: npm run-script build
? Start Command: npm run-script start
? Do you want to use an AWS profile? yes
? Please choose the profile you want to use `your-profile`

Add a Database

Next, we’ll need to add a DynamoDB database to our project. Run amplify add storage in your CLI. This will ask you some questions about your data.

? Please select from one of the below mentioned services: NoSQL Database
? Please provide a friendly name for your resource that will be used to label this category in the project: britneySongStorage
? Please provide table name: britneySongs

? What would you like to name this column: id
? Please choose the data type: string
? Would you like to add another column: Yes
? What would you like to name this column: name
? Please choose the data type: string
? Would you like to add another column: Yes
? What would you like to name this column: year
? Please choose the data type: number
? Would you like to add another column: Yes
? What would you like to name this column: link
? Please choose the data type: string
? Would you like to add another column: No

? Please choose partition key for the table: id
? Do you want to add a sort key to your table? N

? Do you want to add global secondary indexes to your table? No
? Do you want to add a Lambda Trigger for your Table? No

Now, run amplify push -y to deploy your database on AWS!

Create the API

Now, let’s add an API! We’ll create routes for all the CRUD operations and allow database interaction.

We’ll add an API using the Amplify CLI: amplify add api.

You’ll be prompted to answer some questions. First, name your API, then provide a base path for your routes.

? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: britneySongApi
? Provide a path (e.g., /book/{isbn}): /song

Then, we’ll be prompted to create a new AWS Lambda function and name it. We’ll use a Python one.

? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: britneyspearsapi
? Choose the runtime that you want to use: Python

We’ll also need to configure some advanced settings in order to provide access to our database. Use your space bar to select storage and all CRUD actions.

? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the categories you want this function to have access to. storage
? Select the operations you want to permit on britneySongStorage create, read, update, delete

You can then answer “No” to the next few questions, though I’d select yes for editing your local Lambda function. It’ll just open your text editor up to the right file for you!

? Do you want to invoke this function on a recurring schedule? No
? Do you want to configure Lambda layers for this function? No
? Do you want to edit the local lambda function now? Yes
? Restrict API access No
? Do you want to add another path? No

Creating an API will enable Amazon API Gateway which will handle the routing of HTTP requests to our Lambda function.

Finally, run amplify push to deploy your services to the cloud. The -y flag will just skip the confirm step.

$ amplify push -y

Note: you’ll need to re-run this push command whenever you want to deploy changes to your API.

Python Function

First, change into your function’s directory:

cd amplify/backend/function/britneyspearsapi

You’ll need to have pipenv installed for Python packaging. Then, we’ll install the necessary packages.

  • aws-wsgi will allow us to use Flask routing
  • boto3 enables interaction with AWS services in Python, we’ll use it for DynamoDB
  • flask is our web framework
  • flask-cors will handle CORS for our flask app
$ pipenv install aws-wsgi boto3 flask flask-cors

Then, open up your Lambda function’s code — it was opened for you during the amplify add api step, but if you closed it, open up src/index.py within your function’s folder.

There will be a generated “Hello World” Lambda function in your file, you can delete it.

First, we need to initialize a Flask application. We’ll also use flask-cors to handle CORS.

from flask_cors import CORS
from flask import Flask, jsonify, request

app = Flask(__name__)
CORS(app)

Below this code, add a Lambda handler — this function will process our request event. API Gateway is looking specifically for a function called handler so make sure to call it that!

We’ll also use awsgi to use WSGI middleware with API Gateway.

Import awsgi:

+ import awsgi
from flask_cors import CORS
from flask import Flask, jsonify, request

Then add the handler function:

def handler(event, context):
    return awsgi.response(app, event, context)

Above the handler, let’s add our first route! We already created a base path for our API during the amplify add api step above. I’m going to save that route to a variable so that I can prefix all my urls with it. Then, I’ll add a route to my Flask app that handles that /songs/ url and takes GET requests. Right now, I’ll just send a “hello world” message back.

# Constant variable with path prefix
BASE_ROUTE = "/song"

@app.route(BASE_ROUTE, methods=['GET'])
def list_songs():
    return jsonify(message="hello world")

Run amplify push -y to deploy your changes.

To use this route on the frontend, first I’ll install aws-amplify. Do this in your frontend’s root directory instead of your Python function’s.

$ npm i aws-amplify

Then, configure Amplify. If you’re in a create-react-app generated project, put this in the index.js file.

import { Amplify } from 'aws-amplify'
import config from './aws-exports'

Amplify.configure(config)

Then, add your API call. We’ll use aws-amplify‘s API.get to make a GET request to our API. The first argument is the name of the API we created, the second is the route we want to request.

import { API } from 'aws-amplify'

const getData = async () => {
  const data = await API.get('britneySongApi', '/song')
  console.log(data)
}

Add Create Route

Now, let’s make a route that will create a new song in our database. I’ll import boto3uuid, and os and then create a new connection to the database. We’ll also get the name of our DynamoDB table from our environmental variables that Amplify created for us. These are listed in the output of the amplify add api step.

import awsgi
+ import boto3
+ import os

from flask_cors import CORS
from flask import Flask, jsonify, request
+ from uuid import uuid4

+ client = boto3.client("dynamodb")
+ TABLE = os.environ.get("STORAGE_BRITNEYSONGSTORAGE_NAME")
BASE_ROUTE = "/song"

Now, we’ll create our route. This one will still be at the /song url, but this time it will handle a POST request. We’ll get the request object, convert it to json, and then create the item. We’ll provide the name of the table and the data for the item. We’ll randomly generate the id using Python’s uuid4 function. Then, we’ll return a message that the item was created.


@app.route(BASE_ROUTE, methods=['POST'])
def create_song():
    request_json = request.get_json()
    client.put_item(TableName=TABLE, Item={
        'id': { 'S': str(uuid4()) },
        'name': {'S': request_json.get("name")},
        'year': {'S': request_json.get("year")},
        'link': {'S': request_json.get("link")},
    })
    return jsonify(message="item created")

Now, on the frontend, we’ll make a POST request to create the new song!

const data = await API.post('britneySongApi', '/song', { 
  body: { 
    name: 'Toxic', 
    year: '2003', 
    link: 'https://www.youtube.com/watch?v=LOZuxwVk7TU' 
  } 
})
console.log(data)

Add List Route

Let’s update our list route to return a list of all the songs in the database. Change the return on the list_items function to be a table scan.

@app.route(BASE_ROUTE, methods=['GET'])
def list_songs():
+    return jsonify(data=client.scan(TableName=TABLE))

Then on the frontend, you can make this request to see all the songs!

const data = await API.get('britneySongApi', '/song')
console.log(data)

Add Get Route

Now, we’ll add a function that gets one song based on its id. We’ll create a route that handles GET requests to /song/<song_id>. Then, we’ll query the database for the song with that id!

@app.route(BASE_ROUTE + '/<song_id>', methods=['GET'])
def get_song(song_id):
    item = client.get_item(TableName=TABLE, Key={
        'id': {
            'S': song_id
        }
    })
    return jsonify(data=item)

Add Edit Route

Now, we need to add a route to edit an item. This will handle PUT requests to /song/<song_id>. The Key will be the id of the song we want to update. The UpdateExpression will allow us to modify all the attributes of the song. Then the ExpressionAttributeNames provides the key names and the ExpressionAttributeValues provides the values.

@app.route(BASE_ROUTE + '/<song_id>', methods=['PUT'])
def update_song(song_id):
    client.update_item(
        TableName=TABLE,
        Key={'id': {'S': song_id}},
        UpdateExpression='SET #name = :name, #year = :year, #link = :link',
        ExpressionAttributeNames={
            '#name': 'name',
            '#year': 'year',
            '#link': 'link'
        },
        ExpressionAttributeValues={
            ':name': {'S': request.json['name']},
            ':year': {'S': request.json['year']},
            ':link': {'S': request.json['link']},
        }
    )
    return jsonify(message="item updated")

Add Delete Route

Finally, we’ll add a route to delete a song.

@app.route(BASE_ROUTE + '/<song_id>', methods=['DELETE'])
def delete_song(song_id):
    client.delete_item(
        TableName=TABLE,
        Key={'id': {'S': song_id}}
    )
    return jsonify(message="song deleted")

Run amplify push -y to deploy your changes!

Debugging

To debug issues with your API, you can update run amplify mock with a event.json file to match your request’s event object. For example, to test the /song path, you could use an event like this:

event.json

{ "path": "/song", "httpMethod": "GET", "queryStringParameters": ""}

Then run:

$ amplify mock function britneyspearsapi

to test your function locally.

Next Steps

This tutorial demonstrates how to use the AWS SDK with an AWS Amplify-generated Lambda function to build a full CRUD API. You could continue to use other Amplify resources like storage to store the song files, or authentication to restrict the API usage. You could also build out a full frontend to pull the data.

Recommended

Get more insights, news, and assorted awesomeness around all things cloud learning.

Get Started
Who’s going to be learning?
Sign In
Welcome Back!

Psst…this one if you’ve been moved to ACG!