Building a serverless web application on AWS
Here at Enquizit we recently completed a new serverless web application running on AWS. It is our first serverless web application. In this post I will discuss the path we chose, and lessons learned along the way.
The web application we built is a portal for physicians (www.icmecsecondopinion.org). We built this pro bono for International Centre for Missing & Exploited Children (ICMEC). If you are not familiar with icmec.org, then please take a moment to visit their website. ICMEC is on the front lines in the fight against human trafficking. They are bridging the gaps in the global community’s ability to protect children. The work they do is simply amazing.
I also want to give a shout-out to TC Ratnapuri (Founder and president of Enquizit) for funding this project. Thanks for making this possible.
So, let’s dive in…
Here is an overview of the technology stack we ended up with after the project was complete:
On the front end:
- React Router
- AWS Amplify
- Static hosting on S3
On the backend:
- API Gateway
- AWS WAF
How we got there…
When I began my research, I had a difficult time finding articles that described the front and backend of the serverless stack. I knew I wanted to use AWS Amplify, Serverless and React. I eventually discovered Serverless Stack which includes step-by-step instructions on how to build and deploy a serverless application on AWS. It includes AWS Amplify, Serverless and React.js. The tutorials are excellent. I am thankful for all the effort they put into this. There is a lot of content to get through, but every article is well worth your time before you begin building your application. The following sections will cover our approach and the lessons learned that are not covered in the Serverless Stack website.
Our project was organized into two git repositories. The front end React.js site (my-project-react) and the backend api services (my-project-api). This works well, because you are going to deploy the front-end (Static S3 site) and the backend (AWS CloudFormation) separately.
As described in this article we used the Microservices + Mono-Repo approach for our back-end repository. I think this approach works very well. Each service under the /services/ folder includes one serverless.yml and one DynamoDB table. There is a learning curve here if you don’t have much experience with CloudFormation. The most important thing to understand is Cross-Stack References. They allow you to track the dependencies between services. You want to limit these dependencies, but they will become necessary. For example, Cognito will need grant permissions (Group policies) to every API created by your services. We created a separate service just for Cognito. It is deployed last, after every other service, to ensure that we have the most recent ARN for each API. This is done through Cross-Stack References.
Deploying separate stages (Dev/QA/Staging/Prod Environments) is very easy with Serverless. Serverless uses CloudFormation to deploy to AWS. When you deploy a service through Serverless it creates a new CloudFormation stack. You want to be familiar with managing stacks in CloudFormation. If there is a problem updating or deleting a service through Serverless, then you will need to head to the CloudFormation Console to troubleshoot.
DynamoDB can be difficult to understand if you have a relational database background. If that’s you, then you need to read up on Local Secondary Indexes and Global Secondary Indexes. You will use these indexes to create relationships between your DynamoDB tables. Also, read each section listed here to understand how to access your data. Avoid Scans at all costs and don’t be afraid to denormalize the data in your DynamoDB tables.
User permissions can be managed through Cognito and IAM. You will grant users access to AWS recourses using IAM Policies. To achieve this you need a Cognito User Pool and a Cognito Identity Pool. Then create a Cognito User Group and a corresponding IAM Role. The IAM Roles contain IAM Policies that grant access to recourses.
For example, I created “Admin” User Group, “Admin” IAM Role and “Admin” IAM Policy. When a member of the “Admin” Cognito User Group signs into the system, they are given the “Admin” IAM Role.
There is one caveat to using Cognito User Groups to select an IAM Role. The Cognito Identity Pool will not attach the correct IAM Role unless you use the “Choose role from token” method. You can update the setting in the AWS Console here:
Cognito Federated Identity pool -> Edit Identity Pool -> Authentication Providers -> Authentication role selection. Change “Use default role” to “Choose role from token”. Currently, there is no way to do this through CloudFormation.
Finally, security is not covered well (yet) in the Serverless Stack tutorials. You should read up on AWS WAF, AWS Shield and AWS Firewall Manager. Securing your backend APIs is the most important part of building your web application. Definitely do this before you release to Production.
Thanks for reading!