Using Serverless Framework with the Amplify Client Library

Josh Heling
Mar 3 · 13 min read
logos for AWS Amplify, Python, and Serverless Framework
logos for AWS Amplify, Python, and Serverless Framework

This article describes a relatively easy way to use AWS’ Amplify client library to build a serverless app when your backend includes python functions.

Amplify is an AWS product that offers an attractive way to simplify development in some common application scenarios. It includes:

  • Client libraries for common frontend frameworks (React, Angular, Ionic, Vue).
  • A CLI that simplifies provisioning and managing AWS-based backend infrastructure.
  • Glue that connects the frontend and backend.

AWS’ docs for Amplify are pretty good, and include step-by-step tutorials (e.g. this one for React) that will get you up and running quickly in many cases.

Like all batteries-included frameworks, though, there’s a catch. In this case, the detail that caught me was that Amplify’s CLI only supports backend AWS Lambda functions written in javascript.

Amazon is working on removing this limitation, but in the meantime I needed a mostly Python backend for a project. I still wanted to take advantage of the Amplify library on the frontend, and liked the idea of automating things like authz around my backend API (which Amplify does).


Serverless Framework provides a well-known way to simplify deployment of backend bits to AWS (and other cloud providers). It covers similar ground as the CLI part of Amplify, and I knew I could express my desired backend resources using it.

And the Amplify client libraries work fine on their own. So that covers two of the three things Amplify provides.

It’s possible to connect the Amplify client library with the resources provisioned via Serverless Framework via manual configuration, an approach which is detailed in Serverless’ documentation. While this works, it’s pretty brittle, and would require managing different configs for different operating environments, and involve manual work to update the frontend after a new Serverless deployment.

I wanted something closer to the single sourced frontend+backend configuration one gets when using Amplify by itself. Fortunately, I’m not the only one with this desire — Amazon maintains a Serverless plugin that generates Amplify config files. Perfect! That’s just the thing I needed, really.


To recap, what this amounts to is replacing the backend-related use of the Amplify CLI in this project with a combination of Serverless Framework and the aws-amplify-serverless plugin. The Amplify CLI is still used for the frontend hosting / deployment.

The React app in the frontend will use Amplify’s React library, and I’ll be able to use Serverless to connect it (via the plugin) to my python functions. We’ll use AWS Cognito, too, so that only logged in users can access our backend API.

Conceptually, this is straightforward. In practice, it isn’t exactly difficult, but there were a few details that weren’t quite obvious to me at first. The rest of this article describes the step-by-step required to get this set up. Source code for the proof-of-concept outline below is in this repo.


Step 1: Amplify Installation & Configuration

First, we’ll need the Amplify CLI installed:

The command will spawn a browser window to associate the project with an AWS account, and then prompt for some basic settings. It prompts to create a user for Amplify to run as (make sure to take note of the Access Key ID and Secret Access Key), and creates a new AWS profile associated with the newly created user:

Sign in to your AWS administrator account:
https://console.aws.amazon.com/
Press Enter to continue
Specify the AWS Region
? region: us-west-2
Specify the username of the new IAM user:
? user name: amplify-sls-example-user
Complete the user creation using the AWS console
https://console.aws.amazon.com/iam/home?region=undefined#/users$new?step=final&accessKey&userNames=amplify-sls-example-user&permissionType=policies&policies=arn:aws:iam::aws:policy%2FAdministratorAccess
Press Enter to continue
Enter the access key of the newly created user:
? accessKeyId: **********
? secretAccessKey: ********************
This would update/create the AWS Profile in your local machine
? Profile Name: amplify-sls-example
Successfully set up the new user.

Step 2: Basic Frontend App

Now we’ll set up a barebones React app. For this demonstration, the familiar CRA placeholder app will work fine, but there’s nothing CRA-specific we depend on. While these instructions assume React, a similar approach should work for any of the other frontends Amplify supports.

$ npx create-react-app amplify-react-serverless-example
...
Happy hacking!
$

Initialize amplify with — use values that make sense for you, but make sure to choose ‘javascript’ for app type:

$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project ampl-sls-example
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? 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
Using default provider awscloudformation
For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html
? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use amplify-sls-example
Adding backend environment dev to AWS Amplify Console app: d39ymvscqz4gs5
⠴ Initializing project in the cloud...
...
Your project has been successfully initialized and connected to the cloud!
...
$

Add frontend hosting with . Choose the “DEV” option for now and the default options for the other choices, then run to see the vanilla CRA template running.

So far these instructions have directly tracked AWS’ docs (with less detail) — we diverge from them starting in the next section.

Step 3: Serverless Framework Skeleton

Eventually, we’re going to deploy a backend API, and of course we don’t want just anybody to be able to access it. Before we can limit access to our API to logged-in users, though, we need users to be able to log in.

If we were using Amplify for the entire project (like the AWS docs assume), we’d use the command to add an auth backend component. But instead we’re going to define our backend using Serverless Framework. Let’s get that scaffolded out, and then we’ll add auth to it in the next step.

Installing Serverless Framework is as easy as (more details here).

We need some Serverless plugins, too:

$ yarn add -D serverless-wsgi serverless-python-requirements aws-amplify-serverless-plugin

Let’s put a very basic in place, so we start to see how this will come together:

# serverless.yml
service: ampl-sls-example-backend
plugins:
- aws-amplify-serverless-plugin
custom:
stage: ${opt:stage, "dev"}
pythonRequirements:
dockerizePip: non-linux
amplify:
# this can be anything, as long as the frontend project includes it
- filename: src/aws-exports.js
type: javascript
package:
exclude:
- node_modules/**
- amplify/**
- public/**
- src/**
- yarn.lock
provider:
name: aws
stage: ${self:custom.stage}
region: us-west-2

For those both attentive and curious: The section here is needed because we have both the frontend and backend coming from the same project, but don’t want to include all the frontend dependencies in the backend artifact.

Deploy this skeleton of our eventual backend with . Note that we’re specifying the AWS profile created in step 2 above via the environment (the Serverless docs detail other ways to do this — how you choose to specify the profile isn’t important, but using the right profile is).

The Serverless deploy should look something like this:

$ AWS_PROFILE=amplify-sls-example sls deploy
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Validating template...
Serverless: Updating Stack...
Service Information
service: ampl-sls-example-backend
stage: dev
region: us-west-2
stack: ampl-sls-example-backend-dev
resources: 1
api keys:
None
endpoints:
None
functions:
None
layers:
None

There’s really nothing there yet, because we haven’t actually asked Serverless to deploy anything, but this shows us that the is valid. More importantly, if we look at the file, we can see that the is doing its thing and exporting the bits of configuration that the client side of Amplify will need into a simple js structure ready for import:

$ cat src/aws-exports.js
// WARNING: DO NOT EDIT. This file is automatically generated
// Written by aws-amplify-serverless-plugin/1.4.1
const awsmobile = {
aws_project_region: 'us-west-2'
};
export default awsmobile;
$

Right now that’s pretty much nothing, but as we add substance to the Serverless project this file will be the glue that connects our Serverless-defined backend resources to the Amplify client library in our frontend React app.

Step 4: Adding Auth — Backend

We’re going to extend our file to define both a Cognito User Pool and an Identity Pool, which provide authentication and authorization, respectively. (Cognito concepts are covered in appropriate depth elsewhere — this just includes the basics needed to get started.)

It’s possible to define the User and Identity Pools directly in the , but they’re fairly verbose, so it’s nice to keep them in separate files and include them by reference. We’ll to give them a place to live, and then put these files in :

And then reference them from the :

...
resources:
- ${file(resources/cognito-user-pool.yml)}
- ${file(resources/cognito-identity-pool.yml)}

Unfortunately, we can’t yet demonstrate that this is doing anything, because the policies attached to the Identity Pool reference an API Gateway we haven’t yet set up.

Let’s fix that!

Step 5: Create the Backend API

For the sake of this tutorial we’re going to define an absurdly trivial API endpoint. We literally just need something that we can call in order to demonstrate that only authenticated users are able to successfully interact with the API.

A few python dependencies are needed, so take a moment to initialize a virtualenv, switch to it, and install and CORS support:

## change this path to whevever you keep your venvs
$ mkvirtualenv ~/.virtualenvs/amplify-react-sls
...
$ workon amplify-react-sls
$ pip install flask Flask-CORS
## this is important so that serverless knows what deps to bundle
$ pip freeze > requirements.txt

And then create :

# app.py
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
@app.route("/test")
def hello():
return "You must have authenticated correctly."

Our changes in a few places to make this work — here’s what the whole file should look like now:

# serverless.yml
service: ampl-sls-example-backend
plugins:
- serverless-python-requirements
- serverless-wsgi
- aws-amplify-serverless-plugin
custom:
stage: ${opt:stage, "dev"}
wsgi:
app: app.app
packRequirements: false
pythonRequirements:
dockerizePip: non-linux
amplify:
# this can be anything, as long as the frontend project includes it
- filename: src/aws-exports.js
type: javascript
appClient: CognitoUserPoolClient
package:
exclude:
- node_modules/**
- amplify/**
- public/**
- src/**
- yarn.lock
provider:
name: aws
runtime: python3.6
stage: ${self:custom.stage}
region: us-west-2
functions:
app:
handler: wsgi_handler.handler
events:
- http:
path: /test
method: any
cors: true
authorizer: aws_iam
resources:
- ${file(resources/cognito-user-pool.yml)}
- ${file(resources/cognito-identity-pool.yml)}

Most of these changes are standard Serverless config (see this for explanation of most of it). The new section in is important — it references the User Pool client we created above, which is an essential step in getting its config details to be included in the file. The line in the function definition is also important; that tells the API Gateway that will be created to require auth for calls to .

If we now run again, a lot more will happen:

$ AWS_PROFILE=amplify-sls-example sls deploy
...
Serverless: Stack update finished...
Service Information
service: ampl-sls-example-backend
stage: dev
region: us-west-2
stack: ampl-sls-example-backend-dev
resources: 17
api keys:
None
endpoints:
ANY - https://elpcxo65f9.execute-api.us-west-2.amazonaws.com/dev/test
functions:
app: ampl-sls-example-backend-dev-app
layers:
None

And a correspondingly larger set of config data is in , ready to be imported by the React app:

$ cat src/aws-exports.js
// WARNING: DO NOT EDIT. This file is automatically generated
// Written by aws-amplify-serverless-plugin/1.4.1
const awsmobile = {
aws_cloud_logic_custom: [
{
endpoint: 'https://elpcxo65f9.execute-api.us-west-2.amazonaws.com/dev',
name: 'ApiGatewayRestApi',
region: 'us-west-2'
}
],
aws_cognito_identity_pool_id: 'us-west-2:a0a03944-b40b-4d4a-90b0-caf46deda1e5',
aws_cognito_region: 'us-west-2',
aws_project_region: 'us-west-2',
aws_user_pools_id: 'us-west-2_m1yz5KHFQ',
aws_user_pools_web_client_id: '3gntaunleuo30e8mem40laomv'
};
export default awsmobile;

Our new backend API endpoint now exists in AWS’ API Gateway, backed by a lambda function built from . And we can validate that auth is being required with :

$ curl  https://elpcxo65f9.execute-api.us-west-2.amazonaws.com/dev/test
{"message":"Missing Authentication Token"}

We expected this error, of course, since we require auth on the endpoint but didn’t provide any in the request.

So the backend is largely in place now, configured by Serverless Framework. Time to get the React app talking to it!

Step 6: Add Amplify Auth to React

First, let’s integrate our React app with the the Cognito backend resources we set up in step 4. When we’re done with this step, accessing our CRA template will require login. (Of course, this by itself isn’t very interesting, but it’s an important prerequisite to protecting the backend API.)

(Note: this section pretty much mirrors the React side of the AWS docs — check them out for a more detailed step-by-step.)

Add the amplify-related dependencies:

$ yarn add aws-amplify aws-amplify-react

and then import them into :

import Amplify from 'aws-amplify';
// file generated by the sls plugin
import awsconfig from './aws-exports';
import { withAuthenticator } from 'aws-amplify-react';

And then initialize Amplify with the details exported from our Serverless config (by the plugin):

Amplify.configure(awsconfig);

For now we’ll leave the function as-is, but change the line to wrap it with the HOC, which will force valid authenticator before rendering :

export default withAuthenticator(App, true);

Re-build and re-deploy the frontend (with ), and you should find yourself looking at an authentication prompt rather than the stock CRA template:

Screenshot of default Amplify login screen
Screenshot of default Amplify login screen

OK, cool — we wanted to require logins, so this is progress. To create a user, head over the Cognito section of your AWS console, and choose User Pools. You should see a pool whose name corresponds to the and defined in your , like this:

Screenshot of AWS console User Pools view
Screenshot of AWS console User Pools view

Select that pool, then choose “Users and groups” from the left-hand nav rail, and then “Create user”. Go through the user creation process, and use the username/password you created to log in to your deployed app.

On the one hand, this looks like no big deal — it’s just the CRA template app running behind a generic login window. While that’s not so exciting by itself, this is proof that our app now has a backend auth store that our React app knows about. We’ll use that in the next step to make sure only authenticated users can access our backend API.

(For the sake of demonstrating the core thing this article is about, I’m glossing over all kinds of details you can tweak: user account attributes, login form style, and account-related controls in the app once you’ve logged in, just to name a few. See the Amplify docs for more detail.)

Step 7: Call the API

We’re almost there — all that’s left is to wire up our API into the React app in order to demonstrate that the auth credentials our React app gets from logging in are what the API Gateway in front of our backend API is expecting.

First, add the module to our imports from :

// App.js
...
import Amplify, { API } from 'aws-amplify';

Then we’ll add a function that calls the API:

function App() {
const get = async () => {
console.log('calling GET');
const response = await API.get(
'ApiGatewayRestApi', // function defined in our serverless.yml
'/test', // the function's path
{ 'responseType': 'text' }
);
console.log(`got ${response}`);
alert(response);
};
...

and add a crude way to trigger the function to the statement:

...
Learn React
</a>
<button onClick={get}>GET</button>
</header>
...

After one more , you should see a “GET” button that, when clicked, calls our (authentication-required) API. Voila!

Step 8: CORS for Errors

It looks like everything’s working, but there’s one little detail we still need to cover. While the line in 's function definition and the Flask-CORS middleware in together handle the appropriate CORS headers for our backend function, we need to make sure there are CORS headers on errors (potentially) returned from the API Gateway, too. Without this step, instead of any actual error detail we’ll just get a generic CORS error in the console, which isn’t very helpful.

Fortunately, the fix is easy — just include this file in the section of :

resources:
- ${file(resources/cognito-user-pool.yml)}
- ${file(resources/cognito-identity-pool.yml)}
- ${file(resources/api-gateway-errors.yml)}

(This is explained in more detail here.)

Wrapping Up

We’ve done it! Using , we connected an AWS backend defined with Serverless Framework to a React front-end, while keeping configuration of the details automatic.

This is just a very basic example of what can be done with these tools. While this simple app just had a single backend lambda function, it’s easy to also define DynamoDB resources, or S3 support, or use AWS’ AppSync for GraphQL support. And both Amplify and Serverless offer dashboards that can drive CI/CD.

Thanks for reading — now go build something cool.

logos for AWS Amplify, Python, and Serverless Framework
logos for AWS Amplify, Python, and Serverless Framework

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade