‘Run your application without servers’. The idea presented by the cloud service providers is fascinating. Of course, an application runs on virtual servers; however, the servers are not in cloud users’ hands, even invisible to the users, they are automatically allocated and managed by the cloud services. So, the developers can focus more on writing programs, and are released from hardware procurement, installation, and daily administrative tasks. As a result, it can significantly shorten time-to-market in terms of promoting an app to production. Given the nature of its loosely integrated architecture, it can improve the agility of development by a team as well. As a cloud app, it naturally bears all the cloud-specific features such as operational excellence, security, reliability, performance efficiency, cost optimization, and sustainability.
In this blog, we are going to walk through the process of building a serverless web app using AWS and implement a few simple functions. The technologies used in the app are as follows.
AWS Amplify hosts the web site and presents the GUIs to users. A user signs up. The user’s account is created in Cognito. After signed in with the account, the user can browse the tour products and add products to the cart. The user can view what is in the cart as well. The data related to the products and the cart is stored in DynamoDB. Operations on data are executed through a REST API which calls back-end Python Lambda functions.
The source code of the app is available at GitHub.
Process of building a serverless web app
Prerequisites
We’ll use create-react-app to create the front-end app so that make sure you have Node.js and React installed.
Python is another package needed for the app. Please check out Amplify Docs to get the supported version installed. At the same time, make sure you can run pipenv from the terminal or Powershell. To do this, install pipenv and add its folder to the environment variable ‘path’.
As for Amplify, install Amplify CLI and Amplify library and Amplify UI for React.
Before we can use Amplify, we’ll need to configure it first by using ‘amplify configure’ command. Following the instructions, you’ll get there by doing it step by step. You’ll be asked to provide an IAM user during the configuration. Open IAM Console and create an IAM user with AdministratorAcess-Amplify permission granted. Below is the user created for this demo.
Additionally, React Router is used for client-side routing, and Chakra-ui is required for Toast component.
To sum up, here is the short list of installations.
- Node.js
- React
- Python and pipenv
- Amplify (CLI, Library, and UI for React)
- react-router-dom
- Chakra-ui
Create frontend app
Run 'create-react-app' to create the front-end app. We name it as ‘demoserverlesswebapp’.
> create-react-app demoserverlesswebapp
Initialize a new backend
Move to the root folder of the app, run ‘amplify init’ command, and provide the information as necessary.
demoserverlesswebapp> amplify init
After the initialization is done, you can see the app on Amplify Console.
A subfolder ‘amplify’ is added to the local project, as the screenshot shows below.
Add Authentication
Run ‘amplify add auth’, choose ‘Default configuration’.
demoserverlesswebapp> amplify add auth
Choose ‘Username’ for sign in.
For advanced settings, you can skip it if you don’t have a need. Here, I choose ‘Yes’ because I’d like to have preferred username appearing on the signup page and appearing on the menu button after the user sign in.
Leave the following capabilities unchecked.
The auth resource ‘demoserverlesswebapp1e89ccb3’ is added locally.
Run ‘amplify push’ to provision it in the cloud.
demoserverlesswebapp> amplify push
As a result, we have a user pool and an identity pool created in Cognito.
Use the Authenticator component to wrap app as shown by the following image. To make it simple, only a hello message and a sign-out button are displayed on the main page at this point.
Run the app on the local host using ‘npm start’ command.
demoserverlesswebapp> npm start
The sign in page pops up. Amazing!
Click ‘Create Account’ tab, fill out the form, and click ‘Create Account’ button.
You’ll receive an email with a confirmation code. Enter the code, click ‘Confirm’ button.
The account is created in Cognito.
Add Storage
Run ‘amplify add storage’ command.
demoserverlesswebapp> amplify add storage
Choose ‘Content’.
For the storage name, enter ‘swastorage’.
For the bucket name, enter 'swastoragebucket’.
Choose ‘Auth and guest users’.
Select all privileges for authenticated users.
Select ‘read’ for guest users.
Skip adding a Lambda trigger.
Run ‘amplify push’ to provision it in the cloud.
demoserverlesswebapp> amplify push
A new bucket has been created, with several random digits together with the environment identifier appended to the bucket name entered in the previous step.
We create a folder named ’public’ where we store an image ‘product.jpg’ that is shared across users.
Run ‘amplify add api’ command, choose REST.
demoserverlesswebapp> amplify add api
For the name, enter DemoSWAApi.
For the path, enter '/product/items'.
For Lambda function name, enter ‘DemoProductLambda’, which processes requests for products.
For the runtime, choose ‘Python’.
To see what we have for advanced settings, choose ‘yes’.
We chose 'No' for all these settings for this demo.
Choose ‘yes’ for ‘Restrict API access’.
Select all privileges for authenticated users, ‘read’ for guest users.
Let’s add another path for this app.
Lambda Function: DemoCartLambda, which processes requests for user cart.
API access: the same as the previous one
Run ‘amplify push’ to provision it in the cloud.
demoserverlesswebapp> amplify push
When the provision is done, the API endpoint becomes available.
https://g0lz71cmd9.execute-api.us-west-2.amazonaws.com/dev
We can check out more details on API Gateway Console.
Resources
Stages
The Lambda functions come into view on Lambda Console as well. But the source code is not available for edit on the Console, you’ll need to edit it locally.
To consume the REST API in program, we can call Amplify’s APIs and set the required arguments, as the following example shows. It is straightforward.
} from 'aws-amplify/api';
const restOperation = get({
const { body }= await restOperation.response;
const products = await body.json();
console.log('GetProduct call succeeded: ', products);
Deploy
Run ‘amplify add hosting’ command.
demoserverlesswebapp> amplify add hosting
For the plugin module, choose ‘Hosting with Amplify Console’.
For the type, choose ‘Manual deployment'.
Run ‘amplify publish’ to build and publish the app to Amplify.
demoserverlesswebapp> amplify publish
The app’s URL is generated and provided as below.
Copy and paste the URL in a web browser. As you can see, the sign in page shows up.
Open Amplify Console, click ‘demoserverlesswebapp’ under ‘All apps’ on the left pane, we can find the deployment information in ‘Hosting environments’ tab.
Add a few business functions
To enrich the app, let’s add a few business functions.
- A user can view all available products.
- A user can select and add a product to one’s cart.
- A user can view the purchased products in one’s cart and cannot view other users’ items.
- Data of the products and users’ cart must be persistent.
As for GUI, we build a product page to list all products, a cart page to list all purchased items.
As for navigation, we create a dropdown menu that is accessible through the top right button. You click the button; choose the page that you’d like to bring forth.
Cart page
Data is stored in DynamoDB. Product table holds all products whereas UserCart table keeps the cart items.
The frontend app calls REST API deployed on API Gateway to fetch or update data in the database.
The below are the definitions of the tables in DynamoDB. As a Non-SQL database, DynamoDB doesn’t support table join.
table name: Product
partition key: productid, String,
name: String,
badges: String Set
desctext: String,
imgurl: String (image file name)
table name: UserCart
partitionkey: cartid, String
sort key: productid, String
quantity: Number
The project is organized as to components' functions, illustrated by the screenshot below.
Pages go to ‘custom-ui-component’ folder.
Header menu and React Router go to ‘navigation’ folder.
REST API call part goes to ‘service’ folder.
For Amplify part, it organizes the sources by its own rules. In principle, leave the configuration files untouched unless you have mastered how all these configurations work.
For a Lambda function, we can edit the source file ‘index.py’ under its ‘src’ folder and add the required logics.. The details are findable in the app’s source code at GitHub.
To enable a Lambda function to access DynamoDB, we need to grant sufficient privilege to its execution role.
For example, choose 'DemoCartLambda-dev', the Lambda function for the cart.
Go to ‘Edit basic settings’ page.
Open IAM Console by clicking the URL under the execution role.
Add DynamoDB access permission to the role.
We’ll need to do the same to the other Lambda as well.
Lambda function’s execution role
Add AmazonDynamoDBFullAccess permission to the role