Using Amplify for REST APIs and Web hosting
By: Chetan Pawar
Tags: Amplify, Containers, Aws Fargate, Rest Api, AWS

Overview

AWS Amplify is one of the fastest ways that can be used to help front-end web and mobile developers build full stack applications, hosted in AWS.

With Amplify, we can configure app backends and connect our app in minutes, deploy static web apps in a few clicks, and easily manage app content outside the AWS console.

Advantages

Portability of backend

Out of the box infrastructure setup

Minimum efforts for deployment pipeline

What to Build ?

In this Blog we quickly build a REST API using AWS Amplify CLI.

We will build

An ExpressJS server that returns a random number

An ExpressJS server that runs a FizzBuzz algorithm on a Python/Flash random number generator server.

Pre-requisites

Amplify CLI — Open terminal and run npm install -g @aws-amplify/clito update to the latest Amplify CLI.

Amplify CLI configured — If you have not configured the Amplify CLI yet, follow this guide in the documentation.

Setup a new Amplify project

Run the following command to create a new Amplify project called “amplify-rest-containerized” or if you already have an existing Amplify project skip to the next section.

mkdir amplify-rest-containerized

cd amplify-rest-containerized

Initialize an Amplify project by running:

amplify init

For a quick implementation, we can just accept the default values in the amplify init workflow console.

Enable container based deployments

Container-based deployments need to be explicitly enabled. Run amplify configure project to review your project configuration:

amplify configure project

Accept the defaults and answer Yes when asked if you want to enable container-based deployments:

...

? Do you want to enable container-based deployments? Yes

Add a new container-based ExpressJS API

Amplify CLI has the same distribution as any existing API workflow.

Once container-based deployments are enabled, we have the ability to select “REST” → “API Gateway + AWS Fargate (Container-based)” with the amplify add api command.

Amplify CLI supports both GraphQL and REST API options for container based deployments.

We can use container-based deployments alongside existing AppSync and API Gateway + Lambda options.

For the demo, let’s create a REST API.

To create our first container-based REST API, execute:

amplify add api

Choose the following options:

? Please select from one of the below mentioned services:

> REST

? Which service would you like to use

> API Gateway + AWS Fargate (Container-based)

? Provide a friendly name for your resource to be used as a label for this category in the project:

> containerb5734e35

? What image would you like to use

> ExpressJS - REST template

? When do you want to build & deploy the Fargate task

> On every "amplify push" (Fully managed container source)

? Do you want to restrict API access

> No

After a successful completion of the CLI workflow, we’ll see these new files added to the project folder structure.

amplify/backend/api/<your-api-name>

├── amplify.state

├── containerb5734e35-cloudformation-template.json

├── parameters.json

└── src

├── Dockerfile

├── DynamoDBActions.js

├── buildspec.yml

├── index.js

├── package-lock.json

└── package.json

In the src/index.js we will find a starter ExpressJS source code to interact with DynamoDB.

Let’s edit that to return a random number.

Let’s edit that to return a random number. Replace the index.js file with the following code:

const express = require("express");

const bodyParser = require('body-parser');

const port = process.env.PORT || 3001; const app = express();

app.use(bodyParser.json());

app.use(bodyParser.urlencoded({ extended: true })); // Enable CORS for all methods

app.use(function (req, res, next) {

res.header("Access-Control-Allow-Origin", "*")

res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")

next()

}); app.get("/", async (req, res, next) => { try {

res.contentType("application/json").send({

"randomNumber": Math.floor(Math.random() * 101)

})

} catch (err) {

next(err);

}

}); app.listen(port, () => {

console.log('Example app listening at http://localhost:' + port);

});

Deploy

Now let’s deploy our API:

amplify push

What’s happening under the hood ?

Amplify creates APIs as an ECS Service to ensure that your application is monitored and tasks are in a healthy and active state, automatically recovering if an instance fails.

When you make changes to your source code, the build and deployment pipeline will take your source code as inputs.

One or more containers will be built in AWS CodeBuild using your source code and pushed to ECR with a build hash as a tag, allowing you to roll back deployments if something unexpected happens in your application code.

After the build is complete, the pipeline will perform a rolling deployment to launch AWS Fargate Tasks automatically.

Only when all new versions of the image are in a healthy & running state will the old tasks be stopped.

Finally the build artefacts in S3 (in the fully managed scenario) and ECR images are set with a lifecycle policy retention of 7 days for cost optimization.

Test our new containerized ExpressJS API

The best way to demonstrate the containerized API is just by calling it with Postman App.

The API endpoint is printed at the end of the “amplify push” command or when you run “amplify status”.

Deployments for Containers can take a bit longer to build and deploy, but after a few minutes, you can verify the availability by checking the CodePipeline URL printed at the beginning of your “amplify push” command or run “amplify console api”, select the API, and select “CodePipeline”

Note: This is a simple use case just to showcase the workflow. The ExpressJS template also provides out-of-the-box support to create a CRUD interface for a DynamoDB table.

Review Amplify documentation if you’re interested in any other scenario.

Multi-container deployments

Amplify CLI fully relies on a Docker Compose configuration to enable multi-container deployments.

Amplify automatically infers the Fargate and ECS settings based on our app’s Dockerfile or Docker Compose.

Amplify also allows us to have inter-container networking based on the configured ports in your Docker Compose configuration.

To demonstrate that, let’s run amplify add api and

select “REST” → “API Gateway + AWS Fargate (Container-based)” → “Docker Compose — ExpressJS + Flask template” value to add a new multi-container API.

This will create a following folder structure in your amplify/backend/api/<your-api-name>/ folder.

amplify/backend/api/<your-api-name>/

├── amplify.state

├── <your-api-name>-cloudformation-template.json

├── parameters.json

└── src

├── buildspec.yml

├── docker-compose.yml

├── express

│ ├── Dockerfile

│ ├── DynamoDBActions.js

│ ├── index.js

│ └── package.json

└── python

├── Dockerfile

├── requirements.txt

└── src

└── server.py

The top level docker-compose.yml references the express server and the python server.

Docker Compose provides a mechanism to deploy multiple containers at once.

For more information on Docker Compose, please review the official Docker Compose guide.

Docker Compose provides a mechanism to deploy multiple containers at once. For more information on Docker Compose, please review the official Docker Compose guide. This time we’ll have the Python server return a random number and the ExpressJS runs a FizzBuzz algorithm based on the Python server’s random number. Let’s replace our server.py file with the following content:

from flask import Flask

from random import randrange server = Flask(__name__) @server.route('/random')

def hello():

return str(randrange(150)) if __name__ == "__main__":

server.run(host='0.0.0.0')

We created a Flask server that has a /random route which returns a random number between 0 and 150.

Now we edit the express server to interface with the Python server and then run the FizzBuzz algorithm.

Start by replacing the content of the index.js file:

const express = require("express");

const bodyParser = require('body-parser');

const http = require('http');

const port = process.env.PORT || 3000; const app = express();

app.use(bodyParser.json());

app.use(bodyParser.urlencoded({ extended: true })); // Enable CORS for all methods

app.use(function(req, res, next) {

res.header("Access-Control-Allow-Origin", "*")

res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")

next()

}); app.get("/fizzbuzz", (req, res, next) => {

// add networking code to Python server code here

}); app.listen(port, () => {

console.log('Example app listening at http://localhost:' + port);

});

Then add the networking logic to interface with the Python server.

If we test locally with the Docker CLI, reference the Python server by the Docker Compose container name.

If we test locally with the Docker CLI, reference the Python server by the Docker Compose container name. Multiple containers are deployed as a single unit in Fargate (e.g. same Task Definition). This opinionated deployment allows ease of networking between containers on the local loopback interface and avoids extra configuration, costs, operations, and debugging.

Add the following code right below the comment: “// add networking code to Python server code here”

const options = {

port: 5000,

host: 'localhost', // replace with 'python' for local development

method: 'GET',

path: '/random'

};



http.get(options, data => {

var body = '';

data.on('data', (chunk) => {

body += chunk;

});

data.on('end', () =>{

console.log(body);

const randomNumber = body

let fizzOrBuzz = ''

// Add FizzBuzz logic code here



try {

res.contentType("application/json").send({

"newRandomNumber": body,

"fizzOrBuzz": fizzOrBuzz

});

} catch (err){

console.log(err);

next(err);

}

}).on('error', (error) => {

console.log(error);

});

})

Last but not least, add the FizzBuzz algorithm and return the result to the API caller. Add this FizzBuzz algorithm below “//Add FizzBuzz logic here”

if (randomNumber % 15 === 0) {

fizzOrBuzz = 'FizzBuzz'

}

else if (randomNumber % 3 === 0) {

fizzOrBuzz = 'Fizz'

}

else if (randomNumber % 5 === 0) {

fizzOrBuzz = 'Buzz'

}

else {

fizzOrBuzz = randomNumber

}

We’ve got our business logic completed! Let’s deploy our multi-container API by running the following command:

amplify push

Test our new multi-container ExpressJS API

The best way to demonstrate the multi-container API is just by calling it with Postman App.

The API endpoint is printed at the end of the “amplify push” command or when you run amplify status

You should now be able to see the random number and FizzBuzz result returned:

What did we learn ?

This blog post gives a quick way to deploy single and multiple containers using Amplify CLI.

There is much more to explore serverless containers which includes: