Deploy an Angular Application to S3 and CloudFront

Angular is a JavaScript framework for building single-page client applications using HTML and TypeScript. We can easily host the generated static website on Amazon S3 by configuring a CloudFront (CDN) distribution and then uploading the Angular build artefacts to the bucket.

We will automate the AWS infrastructure using AWS CDK, an open-source software development framework to define cloud infrastructure in familiar programming languages and provision it through AWS CloudFormation.

The Cloud Formation Kit (CDK) makes it easy to deploy an application to the AWS Cloud from your workstation by simply running cdk deploy command.

If you have a production application running on any cloud platform, you must think of automating the cloud infrastructure.

For this use case, we will deploy a simple Angular application that will be hosted on S3 via CloudFront. The advantages of including CloudFront in our solution is the benefits of caching (improves performance) and an HTTPS protocol (protect users' connections) for our website.  

Step 1: Initializing a CDK project

AWS CDK currently supports JavaScript, TypeScript, Python, Java, C#, and Go programming languages. We will create a CDK project that uses TypeScript which is my preferred programming language for provisioning the infrastructure:

mkdir deploy-app-to-s3-and-cloudfront
cd deploy-app-to-s3-and-cloudfront
cdk init --language=typescript
Create the starter application

Okay, we initialized a TypeScript project. Now we can start coding our infrastructure in TypeScript.

Step 2: Create an Angular Application

We are going to use the Angular CLI to quickly setup a project in folder called application at the root directory level:

ng new application
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
Setting up an Angular project

Update the script to build your application, this can be found in the application/package.json file:

"build": "ng build --output-path build"
Artefacts to be output in a build folder

The application/src/app/app.component.html defines the UI for our generated application. Feel free to play around and update the template as you wish. The starter project looks like this:

You can start up the Angular application by changing to to application folder and running the npm run start command.

Step 3: Provision the AWS Infrastructure as Code

At the root level lib folder of the CDK project, there's a stack called deploy-app-to-s3-and-cloudfront-stack.ts. Modify the file which defines the application’s infrastructure, to look like the following:  

import { Stack, StackProps, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Bucket } from "aws-cdk-lib/aws-s3";
import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment";
import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
import { ARecord, RecordTarget, HostedZone } from "aws-cdk-lib/aws-route53"
import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets"
import {
    OriginAccessIdentity,
    AllowedMethods,
    ViewerProtocolPolicy,
    Distribution,
} from "aws-cdk-lib/aws-cloudfront";

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

        //Lookup the zone based on domain name
        const zone = HostedZone.fromLookup(this, 'baseZone', {
             domainName: "thabolebelo.com"
        });

        // SSL certificate 
        const certificateArn = Certificate.fromCertificateArn(this, "tlsCertificate", "your_certificate_arn");

        // Web hosting bucket
        const websiteBucket = new Bucket(this, "websiteBucket", {
            versioned: false,
            removalPolicy: RemovalPolicy.DESTROY,
        });

        // Trigger frontend deployment
        new BucketDeployment(this, "websiteDeployment", {
            sources: [Source.asset("application/build")],
            destinationBucket: websiteBucket as any
        });

        // Create Origin Access Identity for CloudFront
        const originAccessIdentity = new OriginAccessIdentity(this, "cloudfrontOAI", {
            comment: "OAI for web application cloudfront distribution",
        });

        // Creating CloudFront distribution
        const cloudFrontDist = new Distribution(this, "cloudfrontDist", {
            defaultRootObject: "index.html",
            domainNames: ["application.thabolebelo.com"],
            certificate: certificateArn,
            defaultBehavior: {
                origin: new S3Origin(websiteBucket as any, {
                    originAccessIdentity: originAccessIdentity as any,
                }) as any,
                compress: true,
                allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
                viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            },
        });

        const applicationURL = new ARecord(this, "appURL", {
            zone: zone,
            recordName: "application.thabolebelo.com",
            target: RecordTarget.fromAlias(new CloudFrontTarget(cloudFrontDist))
        })

        // Output the application URL to test connection
        new CfnOutput(this, "applicationURL", {
            value: applicationURL.domainName,
            exportName: "applicationURL",
        });
    }
}
Provision a CloudFront distribution and associate the S3 origin

Let's dig in and see what this code is going to create and reference in AWS:

  • Importing a Hosted Zone from Route53 so we can create an A record using an existing domain.
  • Importing an already created SSL certificate to enable HTTPS protocol for our application.
  • Creating an Origin Access Identity(OAI) for CloudFront and associate the OAI for the S3 origin. It will restrict the direct access to the S3 bucket and tighten the security.
  • Adding the S3 origin as the default behaviour of the CloudFront distribution.
  • Output out application URL once a deployment is complete.

CDK can also refer to already existing cloud resources in AWS

To enable SSL termination (using HTTPS with CloudFront) for the application, we need to have a certificate already created from the AWS console. We can then reference the Amazon Resource Name (ARN) of the SSL certificate in the stack. Follow the steps below to create a certificate if you don't have one already:

Go to AWS Certificate Manager (ACM) and click Request a certificate, enter domain of your choice:

The certificate is now created and pending validation for CNAME records to be created in Route53:

View the certificate and click Create records in Route53 to have the certificate in a "ready-to-use" state:

Once the records are created in Route53, the certificate can be used for SSL termination:

Step 4: Deploying the Infrastructure to AWS

Since we are referencing existing resources in our CDK stack, we need to update the bin/deploy-app-to-s3-and-cloudfront.ts to use a specific AWS account and region when performing lookups:

import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { DeployAppToS3AndCloudfrontStack } from '../lib/deploy-app-to-s3-and-cloudfront-stack';

const app = new cdk.App();
new DeployAppToS3AndCloudfrontStack(app, 'DeployAppToS3AndCloudfrontStack', {
    env: { account: 'your_account_id', region: 'your_region' },
});

You can  synthesize the CDK app with cdk synth command. It will make sure the CDK code can be compiled to the CloudFormation template without errors. Finally, issue cdk deploy to provision the cloud resources and deploy the application code to AWS:

Summary

We successfully hosted an angular application on S3 by provisioning a CloudFront distribution and associating it with the S3 origin. Feel free to checkout deploy-app-to-s3-and-cloudfront repo on my GitHub if you have any issues