Full Stack Serverless Web Apps with AWS
Web application development has changed vastly during the past few years. This is mainly due to modern architectural styles and patterns which moved from 3 tier monolithic web applications to Microservices and lately for Serverless.
I see Serverless more as a software architecture paradigm shift, using fully managed services as the foundation and connect them in a meaningful way to build modern applications.
Going Serverless is a buzz word today within the software industry, but there is minimal knowledge out there, to start a full stack web application with Serverless on Cloud. This article focuses on how we can design and develop a full stack web application using Serverless Technologies available in AWS. Lets identify the core services I’m going to use for the Web Application stack.
- AWS Lambda — Lambda will be the Compute service where you will deploy your Microservice code. In simple terms, you can write your NodeJS, Python, C# or Java code and deploy it to a Lambda function(s).
- AWS API Gateway — API Gateway will work as the RESTful interface to invoke Lambda code. Apart from providing API related functionalities, it will be the mapping service that maps URLs to Lambda Code (Similar to a Router).
- AWS DynamoDB — DynamoDB is the NoSQL document database available in AWS. This will be used to store the application data.
- AWS CloudFront — This is one of the most important Service which differentiates AWS from other Cloud Platforms. You can use AWS CloudFront as a Content Delivery Network(CDN) and also a Proxy Service. This allows to configure your application domain mapping and SSL certificates (If the Web App users SSL) to CloudFront and CloudFront will be able to route the request to Frontend App as well as the API Gateway based on URL path configurations. e.g You can map /index.html should be served through S3 and /api/* should be served through API gateway.
Tip: To automatically provision the full stack with S3 access best practices, refer my article on Deploying Angular/React Apps in AWS.
- AWS Cognito — AWS Cognito will be used as the Identity Service which will handle Authentication and Authorization(For basic role based authorization schemes). You can implement OpenID connect authentication flow with few clicks using AWS Cognito.
- AWS Route53 — This will be used for Custom Domain mappings to CloudFront. e.g You can map yourdomain.com to AWS CloudFront as a A Record Alias.
- AWS Certificate Manager — This service will be used to provision free SSL certificates issued by AWS.
Tip: To approve SSL certificates, you have a brand new Domain Purchased through Route53, refer my article on Forwarding Emails to your Inbox Using Amazon SES to implement an email forwarding solution to receive Certificate Approval request sent to firstname.lastname@example.org.
Tip: You need to create SSL certificates in Virginia Region for them to available to CloudFront.
Lets see how these services connects with each other. Each of the service connects with each other either through HTTP(s) or internal integrations provided by AWS.
For example you can map API Gateway to AWS CloudFront as an Origin configuring to route traffic to API Gateways URL for a given URL sub path e.g /api/*.
Tip: Here it is important to make sure you whitelist the headers for API Gateway and not forward the Host header. It is also important to configure all the HTTP methods are forwarded with caching TTL set to 0 enforcing HTTPS only.
Although the architecture looks complex (For a person new to AWS) most of the complexities in DevOps can be solved by using Serverless Framework or AWS Serverless Application Model. I would mainly prefer Serverless Framework since it has evolved for several years and simplified the DevOps process also providing support for ThirdParty Plugins.
I’m quite proud to be a part of Serverless Framework becoming the founder of Serverless DynamoDB Local Plugin which became the most popular plugin for DynamoDB with the help of the awesome open source community.
Finally you can end up with a project structure nor different than a code base we are used to before. Following is a project structure I’m using for most of my project at start, keeping things simple.
Services Folder contains a folder for each Serverless Microservice (Serverless Framework Project).
Tip: Start with one Microservice .You can always break the Single Microservice to multiple services later.
Tip: Also don’t share code in between Microservices without putting it in to a versioning system. e.g If you have a shared code, put it to NPM or any other Package management solution so that modifying the code in one Microservice doesn’t effect the other.
Tip: If you are using Serverless Framework, configure the farmework to run the application locally by using Serverless Offline Plugin (To emulate API Gateway and Lambda Locally) and Serverless DynamoDB Local Plugin (To run DynamoDB Locally).
Tip: You can also configure the DynamoDB Table provisioning code & Seeding also inside each Microservice using Serverless Framework Resources and Serverless DynamoDB Local Plugin.
Tip: Use Pub-Sub pattern to sync data between each Microservice’s internal data storage(Which requires propagating changes) using DynamoDB Streams, Lamda and SNS rather invoking multiple API calls from the Frontend client to modify data in different services.
Web App Directory
You might also wonder, why not break the Web App into multiple apps instead of one? I did various research on this and unless you know there will be fully isolated apps (e.g Administrator Page and Web Frontend Application) using a single app would be more productive. From my experience, I feel the Web Technologies are not ready yet to divide the web frontends(Similar to Microservices) and hopefully this will change in future.
Tip: Using a proxy configuration for the Frontend app will simplify the application development where you can avoid challenges in handling Cross Origin Requests (CORS) both implementing at API Gateway and from Browser URL mappings.
For Angular (Its also similar to React using Webpack) you can add the proxy configuration with WebPack as follows.
- Create a file called proxy.config.json in your web app root directory and include the following code. Note: Here if you run the serverless microservice locally you can map to localhost:<port> of the service or directly map to the API Gateway. You can add multiple Microservices to this file and in your Frontend App use relative paths e.g /api/your-service/list-tems.
- In the package.json file modify the start command in script as follows.
You can include the custom CloudFormation Scripts into this directory so that all the provisioning code also version controlled.
Authentication & Authorization
For Authentication you can configure AWS Cognito UserPools and use the Users and Group Feature for Authorization. Under the AWS Cognito Service, there are two sub services available.
- AWS Cognito Userpool- This will be the identity Server and user directory where you store users and their credentials. It will also provide the OpenId Connect authentication flow.
- AWS Cognito Federated Identities — This is useful if your users needs temporal access credentials to access AWS resources (e.g S3 bucket) directly from browser client.
One of the important decisions you need to make here while using Cognito UserPools is that to use Cognito Hosted UI or develop your own UI.
Tip: Though the customizations are limited at the moment, I would prefer to go with Hosted UI since it will provide support for Corporate Federated Logins (e.g Active Directory) and handles their complexities.
Using AWS Cognito UserPools you will receive a id_token after user authenticates with Cognito, which can then be sent to the API Gateway in Authentication header where it is validated using AWS API Gateway Custom Authorizer Lambda function.
At first look, Serverless full stack solutions can look complex, but one important thing to understand the complexity is in the architecture but not in code where an architect can design and implement the foundation of the solution while developers can take over and focus on implementing the Microservices and Frontend application.
Overall implementing Serverless solutions can help you to reap the benefits immediately reducing the Total Cost of Ownership (TCO) of the solution. However it also comes with a cost in embracing the changes in software development practices with a steeper learning curve. From my experience, it is advisable to is to start lean, using fewer services and gradually start applying patterns and practices while you gather knowledge and experience.