Skip to main content

5/100 : Sprint 01 - Day 5

Welcome back 👋.

We are on day 5 of 100 days and will be working on building initial infrastructure using AWS CDK. We will be focusing on just the frontend application today that includes the following infrastructure components (highlighted in the architecture diagram below):

  • S3 bucket
  • CloudFront

High level architecture

Video Walkthrough​

Prerequisites​

Setting up AWS CDK​

We will be using AWS CDK to build our infrastructure. So let's setup the CDK environment first. You may refer to https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html for more details.

Install AWS CDK​

npm install -g aws-cdk
cdk --version

AWS Environment​

Setup the following environments variables in your terminal. You can get the values from your AWS account.

export AWS_ACCESS_KEY_ID=<Your Key ID>
export AWS_SECRET_ACCESS_KEY=<Your Key Secret>
export AWS_DEFAULT_REGION=us-east-1

Bootstrap AWS CDK​

cdk bootstrap aws://ACCOUNT-NUMBER/REGION

Create a new project​

# Create a folder for your project
mkdir <your-app-name>

# Change directory to your project folder
cd <your-app-name>

# Create a new CDK project
cdk init app --language typescript

Create a new stack with S3 bucket and CloudFront​

If this is the first time you are working with any of these AWS Services, you may learn more about them here:

Here is the sample code for the preliminary stack. You will learn more about each of these constructs as you go through the challenge. Feel free to checkout https://cdkworkshop.com/ right away and come back to this challenge.

Note

This is probably the first and last time, a full working code is included in this challenge. Suggest you to try to build the code on your own first and refer to the code only when you get stuck.

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import * as cloudfront_origins from "aws-cdk-lib/aws-cloudfront-origins";
import { CfnOutput, Duration, RemovalPolicy, Stack } from "aws-cdk-lib";

export class MyFS100ChallengeStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Change this to your own app name
const appName = "my-fs100-challenge-web-app";

// Origin Access Identty
const cloudfrontOAI = new cloudfront.OriginAccessIdentity(
this,
"cloudfront-OAI",
{
comment: `OAI for ${appName}`,
}
);

// Content bucket
// This is where the Angular app will be deployed
const siteBucket = new s3.Bucket(this, "SiteBucket", {
bucketName: `${appName}-content`,
publicReadAccess: false,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,

/**
* The default removal policy is RETAIN, which means that cdk destroy will not attempt to delete
* the new bucket, and it will remain in your account until manually deleted. By setting the policy to
* DESTROY, cdk destroy will attempt to delete the bucket, but will error if the bucket is not empty.
*/
removalPolicy: RemovalPolicy.DESTROY, // NOT recommended for production code

/**
* For sample purposes only, if you create an S3 bucket then populate it, stack destruction fails. This
* setting will enable full cleanup of the demo.
*/
autoDeleteObjects: true, // NOT recommended for production code
});

// Grant access to cloudfront
siteBucket.addToResourcePolicy(
new iam.PolicyStatement({
actions: ["s3:GetObject"],
resources: [siteBucket.arnForObjects("*")],
principals: [
new iam.CanonicalUserPrincipal(
cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId
),
],
})
);

new CfnOutput(this, "Bucket", { value: siteBucket.bucketName });

// CloudFront distribution
const distribution = new cloudfront.Distribution(
this,
`${appName}-CF-SiteDistribution`,
{
defaultRootObject: "index.html",
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
errorResponses: [
{
httpStatus: 403,
responseHttpStatus: 403,
responsePagePath: "/error.html",
ttl: Duration.minutes(30),
},
// This is important for Angular routing with deep links to work
// As the app is a single page app, all routes are handled by index.html and those client routes
// actuall do not exist on server. So for all those routes (pages not found), we will return index.html
{
httpStatus: 404,
responseHttpStatus: 200,
responsePagePath: "/index.html",
ttl: Duration.minutes(30),
},
],
defaultBehavior: {
origin: new cloudfront_origins.S3Origin(siteBucket, {
originAccessIdentity: cloudfrontOAI,
}),
compress: true,
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
viewerProtocolPolicy:
cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
}
);

new CfnOutput(this, "DistributionId", {
value: distribution.distributionId,
});

new CfnOutput(this, "DistributionDomainName", {
value: distribution.distributionDomainName,
});

// Deploy site contents to S3 bucket
new s3deploy.BucketDeployment(this, "DeployWithInvalidation", {
// Make sure the source is the dist folder of your Angular app
sources: [s3deploy.Source.asset("webapp/dist/webapp")],
destinationBucket: siteBucket,
distribution,
distributionPaths: ["/*"],
});
}
}

Create a web application​

Install Angular CLI​

npm install -g @angular/cli

Create a new Angular project​

# Create a new Angular project
ng new webapp --routing --style css

# Change directory to the webapp folder
# Optional, update the landing page app.component.html file with placeholder text

Build​

cd webapp   # Change directory to the webapp folder
ng build

Deploy to AWS​

# Change directory to the project folder
cd <your-app-name>

# Synthesize the CloudFormation template
cdk synth

# Deploy
cdk deploy

On first run, it will take some time to create the stack, particularly the CloudFront distribution. Once the stack is created, you can access the application using the Cloudfront Distribution Domain Name (MyFS100ChallengeStack.DistributionDomainName) provided in the output.

Outputs:
MyFS100ChallengeStack.Bucket = my-fs100-challenge-web-app-content
MyFS100ChallengeStack.DistributionDomainName = xxx.cloudfront.net
MyFS100ChallengeStack.DistributionId = xxx

Simply open the xxx.cloudfront.net URL in your browser and you should see the default Angular landing page.

Yay! 🎉 You have successfully deployed your first Angular app to AWS!

Learn more​