Building S3 Static Website with AWS CDK and Projen

Martin Müller

Martin Müller

0
(0)

In this blog post, we will create an awesome React TypeScript App hosted in an S3 static website bucket. For that, we will use AWS CDK and Projen.

But first I will explain what AWS CDK and Projen are and why I strongly recommend using Projen for AWS CDK.

Then we dive into creating the React App which will be afterward deployed in an S3 bucket with AWS CDK.

AWS CDK

AWS CDK (GitHub) stands for Cloud Development Kit and is an open-source framework for creating and managing AWS resources. By using languages familiar to the developer such as TypeScript or Python, the infrastructure is described as code. In doing so, CDK synthesizes the code into AWS Cloudformation Templates and can optionally deploy them right away.

AWS CDK Logo

AWS CDK has been experiencing a steady increase in enthusiastic developers since 2019 and already has a strong and helpful community that is very active on Slack.

There is of course much more to say about AWS CDK and I recommend you to explore it.

Projen

Projen allows sophisticated management of project configuration files through code. With just a few lines of typed JavaScript code, an entire repository can be configured.

Here is an example:

const { AwsCdkTypeScriptApp } = require('projen');

const project = new AwsCdkTypeScriptApp({
  authorAddress: 'damadden88@googlemail.com',
  authorName: 'martin.mueller',
  cdkVersion: '1.99.0',
  cdkVersionPinning: true,
  defaultReleaseBranch: 'main',
  name: 'cdk-s3-website',
});

project.synth();

These few lines create all the GitHub project files your heart desires.

Among them the package.json, .gitignore, tsconfig.json and many many more.

Projen emerged out of the AWS CDK community and contains helpful tools for creating AWS CDK Apps.

Why using Projen instead of CDK directly?

Projen is a lightweight wrapper around CDK so you are in fact using CDK under the hood. Furthermore it helps you with managing dependencies like those from AWS CDK. That’s a huge help as managing those dependencies in a package.json yourself is very time consuming and not pleasant. You will understand what I mean when we start with coding.

Projen Logo

Projen is widely used in the CDK Community and I strongly recommend you trying it.

Here is an awesome-projen list from existing Projen projects.

I really love Projen and use it a lot for my private projects and my work with clients. It lets me iterate and try out AWS CDK code very fast and after using Projen for many month it still feels like cheating <3. As an AWS cloud developer I want to run stuff quickly and not bother wit setting up other stuff like the repository.

It helps me to understand better and faster repos from other creators if they are using Projen for there project. So it becomes a kind of standard.

If you want to know more about Projen have a look into my other Projen posts.

Coding Preparations

Before we start coding we need to install the CDK. Find the concrete instructions here but it is easy just as:

npm i -g aws-cdk

For Projen I recommend setting up an alias as described here as you will run the Projen command quite a lot. Simply write in you shell profile:

alias pj='npx projen'

Cool now you just need to run pj if you want to update the repository.

Let’s do it

Create and checkout a new repository in GitHub or any other git provider. I will take Github. Navigate into the project and open it with VS Code. I usually do:

cd cdk-s3-website
code .

Init the CDK Project

First lets checkout which project types it offers us:

pj new

(or npx projen new if you didn’t do the alias)

It should show you a list of supported project types like those:

  projen new awscdk-app-ts     AWS CDK app in TypeScript.
  projen new awscdk-construct  AWS CDK construct library project.
  projen new cdk8s-app-ts      CDK8s app in TypeScript.
  projen new cdk8s-construct   CDK8s construct library project.
  projen new java              Java project.
  projen new jsii              Multi-language jsii library project.
  projen new nextjs            Next.js project without TypeScript.
  projen new nextjs-ts         Next.js project with TypeScript.
  projen new node              Node.js project.
  projen new project           Base project.
  projen new python            Python project.
  projen new react             React project without TypeScript.
  projen new react-ts          React project with TypeScript.
  projen new typescript        TypeScript project.
  projen new typescript-app    TypeScript app.

Cool or? I think its pretty cool to use Projen for those kinds of projects as it does all the setup for me and I don’t need to figure out each specific way of installing them natively.

Anyway we want to create an AWS CDK app so lets choose the first project type:

pj new awscdk-app-ts

That takes a bit but oh boy look at all those files it added for us like .gitignore, package.json or the tsconfig.json. They all originated from defaults and what is defined in the .projenrc.js file.

Yep, the .projenrc.js file will become very important as it defines the setup of your project. Take your time and explore all those created files a bit.

What I really love about this approach is that those properties in the .projenrc.js file are typed. So with an IDEA like VS Code you just can hover over them ore jump to the definition and you the the documentation about the use of the property.

Let’s tweak the .projenrc.js and see what happens.

I override the project properties and deleted the commented out ones:

const project = new AwsCdkTypeScriptApp({
  authorAddress: 'damadden88@googlemail.com',
  authorName: 'martin.mueller',
  cdkVersion: '1.99.0',
  cdkVersionPinning: true,
  defaultReleaseBranch: 'main',
  name: 'cdk-s3-website',
});

You see I did choose a newer CDK version. At the time of the writing 1.99 was the highest CDK version. If there should be a newer one take that!

Save the change and run:

pj

It will synth your requested changes from the .projenrc.js file with like upgrading the CDK version in the package.json file. After that, it conveniently installs those packages.

In a later section, we will bring in other CDK dependencies like creating an S3 bucket with enabled website hosting. Those will be managed with Projen as well. But before let’s create the React TypeScript App.

React TS App

So for setting up the React TypeScript App we can use Projen as well. We are starting with bringing in the web library and defining the React TypeScript App as child project from our CDK App:

const { AwsCdkTypeScriptApp, web } = require('projen');

const project = new AwsCdkTypeScriptApp({
  authorAddress: 'damadden88@googlemail.com',
  ...
});

project.synth();

const frontendProject = new web.ReactTypeScriptProject({
  defaultReleaseBranch: 'main',
  outdir: 'frontend',
  parent: project,
  name: 'cdk-s3-website',
});

The React App project will be generated in the subfolder called frontend.

For convenience reasons lets implement a Projen script for locally running the react app:

frontendProject.addTask('dev', {
  description: 'Runs the application locally',
  exec: 'react-scripts start',
});

At the end we need to tell Projen to synth the frontend project as well:

frontendProject.synth();

The whole .projenrc.js file should look something like this:

const { AwsCdkTypeScriptApp, web } = require('projen');

const project = new AwsCdkTypeScriptApp({
  authorAddress: 'damadden88@googlemail.com',
  authorName: 'martin.mueller',
  cdkVersion: '1.99.0',
  cdkVersionPinning: true,
  defaultReleaseBranch: 'main',
  name: 'cdk-s3-website',
});

project.synth();

const frontendProject = new web.ReactTypeScriptProject({
  defaultReleaseBranch: 'main',
  outdir: 'frontend',
  parent: project,
  name: 'cdk-s3-website',
});

frontendProject.addTask('dev', {
  description: 'Runs the application locally',
  exec: 'react-scripts start',
});

frontendProject.synth();

Super let synth everything with pj.

Now we go to frontend and run:

yarn dev

If you run into this error:

There might be a problem with the project dependency tree.
It is likely not a bug in Create React App, but something you need to fix locally.

The react-scripts package provided by Create React App requires a dependency:

  "jest": "26.6.0"

Don't try to install it manually: your package manager does it automatically.
...

That is because we basically have two projects configured the aws cdk app and the react app and both using a different version of the jest library. Do the following to ignore the error as it will do us no harm.

Create a file frontend/.env with a one liner:

SKIP_PREFLIGHT_CHECK=true

Ok that was a bit nasty but blame React for it as it produced the error.

OK, let’s run:

yarn dev

If not open automatically you should see a local running React app on https://localhost:3000

Super!

One more thing as we are already in the frontend and folder.

Please run:

yarn build

as we will use the React build to deploy it to an S3 static website bucket in the next section :).

S3 Static Website

Now, lets do some real AWS CDK magic as this blog post is about AWS CDK and we did not use it so far (shame on me).

First we need to add some more CDK S3 dependencies. That is done in the .projenrc.js file with:

const project = new AwsCdkTypeScriptApp({
  ...
  cdkDependencies: [
    '@aws-cdk/aws-s3-deployment',
    '@aws-cdk/aws-s3',
  ],
});

Run

pj

And check the package.json.

We brought in those two dependencies now and it picked up the defined CDK version cdkVersion: '1.99.0'.

Let’s go to the src/main.ts and define the S3 static website:

import * as s3 from '@aws-cdk/aws-s3';
import * as s3deploy from '@aws-cdk/aws-s3-deployment';
import { App, CfnOutput, Construct, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core';

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    const siteBucket = new s3.Bucket(this, 'SiteBucket', {
      websiteIndexDocument: 'index.html',
      websiteErrorDocument: 'error.html',
      publicReadAccess: true,
      cors: [
        {
          allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.HEAD],
          allowedOrigins: ['*'],
          allowedHeaders: ['*'],
          exposedHeaders: ['ETag', 'x-amz-meta-custom-header', 'Authorization', 'Content-Type', 'Accept'],
        },
      ],
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // Deploy site contents to S3 bucket
    new s3deploy.BucketDeployment(this, 'BucketDeployment', {
      sources: [s3deploy.Source.asset('./frontend/build')],
      destinationBucket: siteBucket,
    });

    new CfnOutput(this, 'bucketWebsiteUrl', {
      value: siteBucket.bucketWebsiteUrl,
    });
  }
}

// for development, use account/region from cdk cli
const devEnv = {
  account: 123456789, // you aws account id
  region: 'us-east-1',
};

const app = new App();

new MyStack(app, 'my-stack-dev', { env: devEnv });
// new MyStack(app, 'my-stack-prod', { env: prodEnv });

app.synth();

We defined an S3 static website bucket and told CDK to upload the build files from React in ./frontend/build to it.

As you might noticed I used explicit defined deploy account information instead of those CDK_DEFAULT_ ones.

So instead of:

const devEnv = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

I used:

const devEnv = {
  account: '123456789', // you aws account id
  region: 'us-east-1',
};

This has couples of advantages like that you have you configuration as code and you don’t need to hide it in env variables during builds. As well CDK will make a check that your configured AWS credentials like in ~/.aws/credentials are matching.

That prevents accidental deployment to the wrong account which is really helpful.

OK, you might think but you showing critical aws information with revealing the account id. I do challenge you and that is not true. With only seeing the account id you can not hack into the account. I am happy if you prove me otherwise.

Super!

Let’s deploy it.

Go to the root folder and do:

pj
yarn deploy

CDK will tell you which resources needs to be applied or updated to create the S3 bucket. Confirm it with y.

When the deployment finished you will see the S3 URL like:

 ✅  my-stack-dev

Outputs:
my-stack-dev.bucketWebsiteUrl = http://my-stack-dev-sitebucket397a1860-6hzh1ardmfg0.s3-website-us-east-1.amazonaws.com

Open the URL.

Now you see your React TS App hosted in an S3 bucket.

How cool is that?!

What’s next?

Awesome, we set up a React TypeScript App in an S3 Static Website bucket. From here on we could start to make the React App good looking and create some nice components and routes. And lets bring in some more AWS Services for use in our backend. Like AWS Cognito for user provisioning and authentication.

With AWS Amplify it is very easy to integrate Cognito’s user authentication with our App as it comes with a nice Login UI and Cognito backend integration.

Amplify - Login dialog

The S3 Static Website doesn’t use a Cloud Delivery Network (CDN) so far. A CDN keeps the latency when accessing your website low as it uses a network of servers around the world to cache your site. Setting up a CDN is easily done in AWS CDK.

You could set up an AWS API Gateway to use as a REST Server for our App or even cooler let’s do a GraphQL Server with AWS Appsync as I did here and here.

We hope, you’ve found this article useful! If so, please, help us to spread it to the world!

Stay tuned for the next blog post 🙂

How useful was this post?

Click on a star to rate it!

As you found this post useful...

Follow us on social media!

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Top rated Udemy Courses to improve you career

Subscribe to our updates

Like this article?

Share on facebook
Share on Facebook
Share on twitter
Share on Twitter
Share on linkedin
Share on Linkdin
Share on pinterest
Share on Pinterest

Want to be an author of another post?

We’re looking for skilled technical authors for our blog!

Leave a comment

If you’d like to ask a question about the code or piece of configuration, feel free to use https://codeshare.io/ or a similar tool as Facebook comments are breaking code formatting.