Back to home

AWS CDK With Typescript Foundations

Published: Aug 6, 2021

Last updated: Aug 6, 2021

This post is #1 of a multi-part series on the AWS CDK with TypeScript written during Melbourne lockdown #6.

This post will be an introduction to working with the AWS CDK in TypeScript.

It will lay the foundations for future posts in this series by introducing a simply example of a simple CDK application and speaking to some useful resources and definitions. This is a series where I will write one post for each day of our current lockdown in Melbourne (lockdown #6 that is expected to last seven days).

Source code can be found here.

Prerequisites

  1. An AWS account is required to run this example.
  2. AWS credentials will need to be set up on your local machine.

Getting started

We will follow the AWS CDK Getting Started with TypeScript Guide to get started.

$ mkdir aws-cdk-with-typescript-foundations $ cd aws-cdk-with-typescript-foundations $ npx cdk init app --language typescript

This will kick off the initial setup of the project.

At this stage, a working TypeScript application is ready for us to use.

Upgrading the CDK packages to the latest version

The package npm-check-updates allows us to check and upgrade AWS CDK packages.

It is very useful when working with different AWS CDK modules to ensure version parity and to avoid version conflicts.

Within package.json, let's add a helper script to check and upgrade the CDK packages.

{ "scripts": { "check": "npx npm-check-updates \"/aws-cdk/\"", "upgrade": "npx npm-check-updates -u \"/aws-cdk/\" && npm install" } }

Some scripts omitted for brevity. The script names are also up to your discretion, but I will work with check and upgrade in this example.

Now, we can run npm run check to check for updates and npm run upgrade to upgrade the packages.

An example output from my console when running the commands:

$ npm run check > aws-cdk-with-typescript-foundations@0.1.0 check > npx npm-check-updates "/aws-cdk/" Checking /Users/dennisokeeffe/code/blog-projects/aws-cdk-with-typescript-foundations/package.json [====================] 3/3 100% @aws-cdk/assert 1.108.1 → 1.117.0 aws-cdk 1.108.1 → 1.117.0 @aws-cdk/core 1.108.1 → 1.117.0 Run ncu -u to upgrade package.json $ npm run upgrade > aws-cdk-with-typescript-foundations@0.1.0 upgrade > npx npm-check-updates -u "/aws-cdk/" Upgrading /Users/dennisokeeffe/code/blog-projects/aws-cdk-with-typescript-foundations/package.json [====================] 3/3 100% @aws-cdk/assert 1.108.1 → 1.117.0 aws-cdk 1.108.1 → 1.117.0 @aws-cdk/core 1.108.1 → 1.117.0 Run npm install to install new versions. added 180 packages, changed 10 packages, and audited 740 packages in 22s 25 packages are looking for funding run `npm fund` for details found 0 vulnerabilities

At this point, your AWS CDK projects will be up to date.

An overview of the base project structure

Using the tree command (which I installed via Brew for MacOS), I can print out the base project structure for us to explore:

tree -L 2 -I node_modules . ├── README.md ├── bin │ └── aws-cdk-with-typescript-foundations.ts ├── cdk.json ├── jest.config.js ├── lib │ └── aws-cdk-with-typescript-foundations-stack.ts ├── package-lock.json ├── package.json ├── test │ └── aws-cdk-with-typescript-foundations.test.ts └── tsconfig.json 3 directories, 9 files

Here is an overview of the most import folders:

FolderDescription
binThe bin folder contains the TypeScript entry point for the application.
libThe lib folder contains the TypeScript source code for the application.
testThe test folder contains the TypeScript source code for the tests.

As for the important files (not included package.json):

FileDescription
cdk.jsonThe cdk.json file contains the configuration for the app and tells the CDK CLI how to execute the code.
jest.config.jsThe configuration file for the Jest test runner.
tsconfig.jsonThe TypeScript configuration file.

Exploring the cdk.json file

If we look at the cdk.json file, we can see (at the time of writing) that it contains the following information:

{ "app": "npx ts-node --prefer-ts-exts bin/aws-cdk-with-typescript-foundations.ts", "context": { "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, "@aws-cdk/core:enableStackNameDuplicates": "true", "aws-cdk:enableDiffNoFail": "true", "@aws-cdk/core:stackRelativeExports": "true", "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, "@aws-cdk/aws-kms:defaultKeyPolicies": true, "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, "@aws-cdk/aws-lambda:recognizeVersionProps": true } }

The app key contains the command that the AWS CDK CLI will execute to start the application.

For example, if we run npm run cdk synth to generate the application, it will use ts-node to enable us to run the bin/aws-cdk-with-typescript-foundations.ts without the need to transpile the file to JavaScript first.

The context key contains an object that toggles AWS CDK feature flags.

An overview of commands

The AWS CDK project comes with a list of useful commands to help you get started.

CommandDescription
npm run buildCompile TypeScript to JavaScript
npm run watchWatch for changes and compile
npm run testPerform the Jest unit tests
cdk deployDeploy this stack to your default AWS account/region
cdk diffCompare deployed stack with current state
cdk synthEmits the synthesized CloudFormation template
cdk destroyTear down your deployed stack

When we work through all of our applications throughout this series, we will make significant use of these commands.

Creating our first stack

In our first example, we are simply going to create a simple stack that creates an S3 bucket.

To do so, we will need to install the @aws-cdk/aws-s3 package.

$ npm install --save @aws-cdk/aws-s3

It is important that all package versions for the @aws-cdk packages are at parity. If you run into any issues moving forward, ensure they are all at the latest.

To create our example bucket, we will update the lib/aws-cdk-with-typescript-foundations-stack.ts file to the following:

import * as cdk from "@aws-cdk/core"; import * as s3 from "@aws-cdk/aws-s3"; export class AwsCdkWithTypescriptFoundationsStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here new s3.Bucket(this, "MyFirstBucket", { bucketName: "hello-aws-cdk-my-first-bucket", removalPolicy: cdk.RemovalPolicy.DESTROY, }); } }

This example is contrived, but it will allow us to create our first S3 bucket hello-aws-cdk-my-first-bucket.

For each CDK module, the documentation is your best friend. The AWS CDK S3 documentation can help us to explain our new s3.bucket invocation.

The overview documentation shows that new Bucket(this, 'MyFirstBucket'); is the basic example of creating an unencrypted s3 bucket. The pattern for all constructs is that you pass the scope as the first argument (this in our case), the name reference for this construct and, depending on the construct, the props object.

If we move to explore the Bucket construct documentation, we can see that all construct props are optional.

In our code, we have added the optional prop bucketName to the new s3.Bucket(this, "MyFirstBucket", { ... }); invocation in order to control the naming of our bucket.

We are also explicitly adding a removalPolicy to be the enum value cdk.RemovalPolicy.DESTROY which will cause the bucket to be deleted when the stack is destroyed.

At this stage, we are ready to deploy our CDK stack. First, however, we will update our first test to reflect our new code.

Updating our first test

Within test/aws-cdk-with-typescript-foundations.test.ts, we have a test file that will test the empty stack created when we initialized the project.

We can run tests by running npm run test or the shorthand npm t. Doing so will lead to a failed test (thanks to our change):

$ npm t > aws-cdk-with-typescript-foundations@0.1.0 test > jest Resources [+] AWS::S3::Bucket MyFirstBucketB8884501 FAIL test/aws-cdk-with-typescript-foundations.test.ts ✕ Empty Stack (139 ms) ● Empty Stack Template comparison produced unacceptable match 16 | ); 17 | // THEN > 18 | expectCDK(stack).to( | ^ 19 | matchTemplate( 20 | { 21 | Resources: {}, at StackMatchesTemplateAssertion.assertOrThrow (node_modules/@aws-cdk/assert/lib/assertions/match-template.ts:38:13) at StackInspector._to (node_modules/@aws-cdk/assert/lib/inspector.ts:25:15) at StackInspector.to (node_modules/@aws-cdk/assert/lib/inspector.ts:15:14) at Object.<anonymous> (test/aws-cdk-with-typescript-foundations.test.ts:18:20) -------------------------------------------------------------------------------------- { "Resources": { "MyFirstBucketB8884501": { "Type": "AWS::S3::Bucket", "Properties": { "BucketName": "hello-aws-cdk-my-first-bucket" }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" } } } Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 3.358 s Ran all test suites.

The output tells us that the test failed and gives an output of the Resources object that was created. Given that we have confirmed that this is the expected output, we can copy that value and modify our test file:

import { expect as expectCDK, matchTemplate, MatchStyle, } from "@aws-cdk/assert"; import * as cdk from "@aws-cdk/core"; import * as AwsCdkWithTypescriptFoundations from "../lib/aws-cdk-with-typescript-foundations-stack"; test("Empty Stack", () => { const app = new cdk.App(); // WHEN const stack = new AwsCdkWithTypescriptFoundations.AwsCdkWithTypescriptFoundationsStack( app, "MyTestStack" ); // THEN expectCDK(stack).to( matchTemplate( { Resources: { MyFirstBucketB8884501: { Type: "AWS::S3::Bucket", Properties: { BucketName: "hello-aws-cdk-my-first-bucket", }, UpdateReplacePolicy: "Delete", DeletionPolicy: "Delete", }, }, }, MatchStyle.EXACT ) ); });

Rerunning the test now gives us the following output:

$ npm t > aws-cdk-with-typescript-foundations@0.1.0 test > jest PASS test/aws-cdk-with-typescript-foundations.test.ts ✓ Empty Stack (106 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.504 s, estimated 4 s Ran all test suites.

Success! Our stack now has tests for the expected output resources.

Deploying the stack

To deploy the stack, we need to synthesize and deploy the stack. In order to do this, it is assumed that your environment is setup with the correct AWS credentials.

To synthesize and deploy, let's run the following:

$ npm run cdk synth > aws-cdk-with-typescript-foundations@0.1.0 cdk > cdk "synth" Resources: MyFirstBucketB8884501: Type: AWS::S3::Bucket Properties: BucketName: hello-aws-cdk-my-first-bucket UpdateReplacePolicy: Delete DeletionPolicy: Delete Metadata: aws:cdk:path: AwsCdkWithTypescriptFoundationsStack/MyFirstBucket/Resource CDKMetadata: Type: AWS::CDK::Metadata Properties: Analytics: v2:deflate64:H4sIAAAAAAAAEyWNSwrDMAxEz9K9o9SU0C5Lc4P0BEFWwTGVwJLThfHdm89qHswbxoP3d7henvNPOwypryiZoL5txuRGYbVc0Nz44YlUSkbaeStCtCjc3D7UG9RXwUSHeFJrjiUQLNqvfoDHdrJojF0ubPFLMJ35BzafENSBAAAA Metadata: aws:cdk:path: AwsCdkWithTypescriptFoundationsStack/CDKMetadata/Default Condition: CDKMetadataAvailable Conditions: CDKMetadataAvailable: Fn::Or: - Fn::Or: - Fn::Equals: - Ref: AWS::Region - af-south-1 - Fn::Equals: - Ref: AWS::Region - ap-east-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-2 - Fn::Equals: - Ref: AWS::Region - ap-south-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-2 - Fn::Equals: - Ref: AWS::Region - ca-central-1 - Fn::Equals: - Ref: AWS::Region - cn-north-1 - Fn::Equals: - Ref: AWS::Region - cn-northwest-1 - Fn::Or: - Fn::Equals: - Ref: AWS::Region - eu-central-1 - Fn::Equals: - Ref: AWS::Region - eu-north-1 - Fn::Equals: - Ref: AWS::Region - eu-south-1 - Fn::Equals: - Ref: AWS::Region - eu-west-1 - Fn::Equals: - Ref: AWS::Region - eu-west-2 - Fn::Equals: - Ref: AWS::Region - eu-west-3 - Fn::Equals: - Ref: AWS::Region - me-south-1 - Fn::Equals: - Ref: AWS::Region - sa-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-2 - Fn::Or: - Fn::Equals: - Ref: AWS::Region - us-west-1 - Fn::Equals: - Ref: AWS::Region - us-west-2

The will be a new cdk.out folder created at the root of the project with the synthesized output. I won't go into that folder, but feel free to explore the output files.

We can now deploy our project with npm run cdk deploy.

$ npm run cdk deploy > aws-cdk-with-typescript-foundations@0.1.0 cdk > cdk "deploy" AwsCdkWithTypescriptFoundationsStack: deploying... AwsCdkWithTypescriptFoundationsStack: creating CloudFormation changeset... ✅ AwsCdkWithTypescriptFoundationsStack Stack ARN: arn:aws:cloudformation:us-east-1:178467697118:stack/AwsCdkWithTypescriptFoundationsStack/95c25ac0-f649-11eb-ab9c-123cb1b397a4

Depending on your stack, you may get a section requiring approval for a change to identity and access management rules based on your constructs and permissions required for them.

Validating our stack that deployed

We can use the AWS CLI to confirm that our bucket was created.

Assuming, you have the CLI installed, run the following to list your buckets and grep for the specified name:

$ aws s3 ls | grep hello-aws-cdk-my-first-bucket 2021-08-06 10:02:39 hello-aws-cdk-my-first-bucket

Success! We now have a bucket that we can use to store our data.

Destroying the stack

To ensure we are not paying for unused resources, it is important that we tear down our stack after usage.

We can do so with the cdk destory command.

Run the following and select y to confirm the tear down:

$ npm run cdk destroy > aws-cdk-with-typescript-foundations@0.1.0 cdk > cdk "destroy" Are you sure you want to delete: AwsCdkWithTypescriptFoundationsStack (y/n)? y AwsCdkWithTypescriptFoundationsStack: destroying... ✅ AwsCdkWithTypescriptFoundationsStack: destroyed

We can confirm the bucket was destroyed with the AWS CLI:

$ aws s3 ls | grep hello-aws-cdk-my-first-bucket # empty response

Awesome! We have gone through an entire lifecycle of a stack deployment using the AWS CDK with TypeScript.

Understanding the constructs

We have breezed through the basic concepts of the AWS CDK. However, it would be remiss not to link to some resources to better understand some of the concepts that we have skimmed over.

The kevinslin/open-cdk is a great guide for AWS CDK best practices.

I highly recommend having a read, but for now I will speak to the three types of constructs:

ConstructDescription
Low levelConstructs which are automatically generated from and map 1:1 to all CloudFormation resources and properties - they have a CfnXXX prefix.
High levelHigh Level constructs that initialize CFN resources with sane defaults and provide a high level interface to said resource.
CDK pattern constructsConstructs which stitch together multiple resources together, for example the LambdaRestApi construct which creates an api gateway that's backed by Lambda.

In our example above, we used a high level construct to initialize our S3 bucket. In future posts, we will work with custom constructs and building out CDK pattern constructs.

Summary

Today's post demonstrated how to get started with an new project on the AWS CDK with TypeScript.

We went over the project structure, the basic commands, construct definitions as well as going through an entire lifecycle of a stack deployment using the AWS CDK with TypeScript to deploy our first S3 bucket.

This is just the start of the series and over the coming days we will work through local development as well as more complex examples on deploying CDNs and lambda functions.

Resources and further reading

Photo credit: fellowferdi

Personal image

Dennis O'Keeffe

@dennisokeeffe92
  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on Visibuild.

1,200+ PEOPLE ALREADY JOINED ❤️️

Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.

AWS CDK With Typescript Foundations

Introduction

Share this post