Host Frontend with AWS API Gateway-S3 integration

Host Frontend with AWS API Gateway-S3 integration

Configure multiple frontends to be hosted on a single S3 bucket exposed through API Gateway using CloudFormation

Context

  1. I want to be able to use a single API Gateway to direct traffic to multiple frontend applications stored in a central S3 bucket.

  2. I want an approach to define the API path in API Gateway that is scalable and without having to create the paths manually.

Key Concepts

API Gateway Resource Configurations

  1. In the paths defined in the API Gateway's API, we have /{fe-target} and /{fe-target}/static/{subfolder+}.

  2. The common thought would be "Why not just use proxy resource?"

    • Other than potential increased in security concerns of path transversal of gaining unintended access to other folders/files in the S3 bucket

    • On a more functionality focus point of view, you cannot create child resource under a resource with a greedy path

  3. Using parameter i.e. resource defined in curly braces, you allow the flexibility of taking different values while setting the boundaries of resources to access for the frontend static resources.

  4. For example, the Invoke URL of my API Gateway is https://example.execute-api.ap-southeast-1.amazonaws.com/portals I have multiple frontend static resources stored in my S3 bucket sample-s3-bucket, web1 and web2

    • The Invoke URL to access web1 will be https://example.execute-api.ap-southeast-1.amazonaws.com/portals/web1

    • The path override used is sample-s3-bucket/{fe-target}/index.html

    • To map the value of {fe-target} from the method request, the URL path parameter will need to be defined with Path parameter, method.request.path.fe-target under the Integration request settings

React App Build

  1. Due to way we are storing each frontend production build folder in S3 bucket, we can say that the frontend is not hosted at the root URL.

  2. By default, the React App is build assuming the frontend is hosted on a root URL.

  3. Leveraging on the advanced configurations of building React applications, the React frontend app will be built correctly as though they are hosted at a non-root URL. This is because Create React app will substitute the %PUBLIC_URL% with a correct absolute path when npm run build is executed

  • BUILD_PATH: This variable is to specify a new path for Create React App to output assets. This should be specified as a path relative to the root of your project.

  • PUBLIC_URL: Create React App assumes your application is hosted at the serving web server's root; this variable to force assets to be referenced verbatim to the URL you provide (hostname included). This may be particularly useful when using a CDN to host your application.


Environment Setup

AWS Resources

Using AWS rain command to create the boilerplate and define the following AWS resources to be provisioned using CloudFormation(CF):

  • S3 bucket and its bucket policy

  • API Gateway with REST API type

  • IAM role to be used by API Gateway

Please refer to this link for the CloudFormation template to provision the required AWS resources.

Sample Web Application

I will be leveraging on the Official AWS sample React CORS-friendly Single Page Application with some modifications made to show different images.

Creating production build

I will be building 2 production builds with 2 names for PUBLIC_URL and BUILD_PATH, i.e. web1 and web2 respectively. The steps below are for in the context of creating a production build for web1.

  1. In order for NPM to build the production build as intended, you will need to first define a dotenv file, .env containing the following environment variables

    • BUILD_PATH

    • PUBLIC_URL

# Sample .dotenv file when running for value `web1`
BUILD_PATH='web1'
PUBLIC_URL='web1/'
  1. Install the required dependencies with npm install

  2. Run npm run build to create a production build. It should show similar output as shown below

    • You can see that the build has been created with the project being built assuming it is hosted at the specified PUBLIC_URL path
% npm run build

> react-cors-spa@1.0.0 build
> react-scripts build

Creating an optimized production build...
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
Compiled successfully.

File sizes after gzip:

  48.52 kB  web1/static/js/main.31d56e89.js
  673 B     web1/static/css/main.4a727023.css

The project was built assuming it is hosted at web1/.
You can control this with the homepage field in your package.json.

The web1 folder is ready to be deployed.

Find out more about deployment here:

  https://cra.link/deployment
  1. You should have a similar structure from your production build
% tree web1
web1
├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── static
    ├── css
    │   ├── main.4a727023.css
    │   └── main.4a727023.css.map
    ├── js
    │   ├── main.31d56e89.js
    │   ├── main.31d56e89.js.LICENSE.txt
    │   └── main.31d56e89.js.map
    └── media
        ├── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg
        ├── logoCloudFront.2ea0429935d363011374.png
        └── logoS3.aaefa311c47140b02bed.png

5 directories, 15 files

Final Setup & Testing

  1. Upload the production build folder, web1 into the provisioned S3 bucket

    • In my case, I will be uploading web1 and web2 into the S3 bucket

  1. Deploy the API to a new stage for the API Gateway

  1. Now, test accessing the different frontends by using the Invoke URL for the API Gateway!

Caveats and Considerations

  1. This approach will only work if all the frontend production build project structure are all the same.

    • This is considering that we are leveraging on the path override feature in the Integration request setting to access the intended resource in the S3 bucket.
  2. If you would like to have more intuitive URLs for your API users, consider using custom domain names for your API Gateway.


Resources

Did you find this article valuable?

Support Nuggets of Wisdom by becoming a sponsor. Any amount is appreciated!