Getting Started with the AWS CDK using TypeScript
This post will give you an overview of how to get up and running with the AWS CDK using TypeScript. We’ll go over setting up the project, bootstrapping your AWS Accounts, and making your first deployment. Lets get started!
Install the CDK CLI
Like most software these days, the CDK has a CLI for you to use in the terminal. Interestingly, this CLI is not part of the base AWS CLI and will need to be installed separately.
The easiest way to install the CLI is via npm:
npm install -g aws-cdkOr, if you’re on MacOS and prefer using Homebrew:
brew install aws-cdkOnce it’s done, you can double check that it’s installed correctly by running:
cdk --versionCreating a New CDK Project
Now that we have the CLI installed, let’s boostrap a new CDK project.
First navigate to where you want to setup your project, and then create a new directory for it:
mkdir my-cdk-projectcd my-cdk-projectIn the project directory, run the following command to create your project:
cdk init app --language typescript --generate-onlyIf everything is successful, you should see the CLI output something like:
Applying project template app for typescript# Welcome to your CDK TypeScript project
This is a blank project for CDK development with TypeScript.
The `cdk.json` file tells the CDK Toolkit how to execute your app.
## Useful commands
* `npm run build` compile typescript to js* `npm run watch` watch for changes and compile* `npm run test` perform the jest unit tests* `npx cdk deploy` deploy this stack to your default AWS account/region* `npx cdk diff` compare deployed stack with current state* `npx cdk synth` emits the synthesized CloudFormation template
✅ All done!You may see some additional stuff as well, you can just ignore that (for now).
Now that we’ve generated the project, let’s install the dependencies from npm and then we’ll look at everything that was generated.
npm installRunning a ls you should see that the CDK has created a couple of new files and directories for you.
Project Structure
Let’s take a closer look at everything that was generated.
First up, the usual package.json and package-lock.json that are used by npm to manage dependencies.
Next up is the cdk.json file, which has a few important things in it:
- The
appfield tells CDK run your code (by default for TypeScript this usests-node). - The
watchfield tells the CDK which files to monitor for changes in watch mode. - The
contextfield is a list of feature flags set by the CDK.
As this is a TypeScript project, we of course have the tsconfig.json file for the compiler.
We also get jest.config.js and accompanying test directory for unit tests - this is functional, however as I typically prefer to use vitest for unit testing, we will be swapping this out later.
Finally we have the bin and lib directories. Addmittedly I’ve never been quite sure why the CDK team has opted for this structure, an setting up a hashbang script in the bin dir. I suspect this is to attempt to follow the same rough project shape as other languages.
In any case, we’re going to make a couple of changes here to make working with the CDK a whole lot easier.
Project Setup
The first thing we’re going to do is swap out ts-node for tsx and configure out CDK app entrypoint.
Start by installed tsx as a dependency:
npm install tsxAnd uninstalling ts-node:
npm uninstall ts-nodeI also prefer a src directory over lib (this isn’t library code after all), so we can remove the bin and lib directories and create a new src directory:
rm -rf bin libmkdir srcNext let’s create our app’s entrypoint file at src/index.ts:
touch src/index.tsAnd add the following code to it with your preferred editor:
import * as cdk from 'aws-cdk-lib';
const app = new cdk.App();Finally, we need to update our cdk.json file to use tsx:
{ "app": "npx ts-node --prefer-ts-exts bin/buildcdk-examples.ts", "app": "npx tsx src/index.ts", // rest of the cdk.json file}Adding our First Stack
Now that our project is setup, let’s add our first stack!
For the sake of the example, we’re just going to deploy a S3 bucket to a single AWS Account.
Start by creating a directory for stacks and creating a new stack file:
mkdir src/stackstouch src/stacks/app-stack.tsLet’s add our infra-as-code:
import * as cdk from 'aws-cdk-lib';import * as s3 from 'aws-cdk-lib/aws-s3';
export class AppStack extends cdk.Stack { constructor(stage: cdk.Stage) { super(stage, 'app', props);
new s3.Bucket(this, 'example-bucket', { bucketName: 'bcdk-example-bucket-123456', versioned: true, }); }}Now we have our stack, but before we can add it to our CDK app, we should define a Stage for our stack to be able to specify where it’s going to deploy to.
Defining a Stage
Let’s create a new file for our stage:
mkdir src/stagestouch src/stages/dev-stage.tsWe’re going to assume for this example that the AWS Account we’re deploying to is our “Development” environment.
import * as cdk from 'aws-cdk-lib';
export class Dev extends cdk.Stage { constructor(app: cdk.App) { super(app, 'dev', { env: { account: 'YOUR ACCOUNT ID HERE', region: 'us-east-1', }, }); }}Make sure to replace YOUR ACCOUNT ID HERE with your actual AWS Account ID.
If you don’t know your AWS Account ID, you can run the following command using the AWS CLI to get it:
aws sts get-caller-identity --query Account --output textNow that we have our stage defined to tell the CDK where to deploy to, we can finally add both our stage and stack to our CDK app:
import * as cdk from 'aws-cdk-lib';import { Dev } from './stages/dev-stage';import { AppStack } from './stacks/app-stack';
const app = new cdk.App();const dev = new Dev(app);
new AppStack(dev);This is all looking good, we’re now ready to deploy!
Bootstrapping the AWS Account
Before we can deploy anything, we first need to “bootstrap” the AWS Account.
This is just a one-time setup step that the CDK requires us to do every time we want to deploy to a new AWS Account and Region.
Luckily it’s just a single command:
cdk bootstrap aws://YOUR-ACCOUNT-ID/us-east-1(Make sure you of course to replace YOUR-ACCOUNT-ID and have proper credentials setup in your terminal)
But what is this actually doing, and why do we need to do it?
Under the hood, the CDK runs on top of CloudFormation. This “bootstrapping” setup is creating a dedicated CFN stack in your AWS Account that contains a bunch of metadata and resources (like an S3 bucket for storing assets) that the CDK needs to be able to deploy your infrastructure.
We can verify this by going to the CFN console and looking for a stack called CDKToolkit in the same Account and Region we just bootstrapped.
Deploying your First Stack
Okay, now we’re actually ready to deploy that first stack!
We can double check that things are setup correctly by looking at the list of stacks in our CDK app:
cdk listWe should see it output this:
dev/app (dev-app)This is how we reference our stack when using the CDK CLI, using this <stage>/<stack> format.
One good habit you should get used to doing every time before deploying is to run a diff for the stack you’re deploying to verify it’s what you expect to happen.
cdk diff dev/appThis should output something like:
Stack dev/app (dev-app)Parameters[+] Parameter app/BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}
Resources[+] AWS::S3::Bucket app/example-bucket examplebucketC9DFA43E
✨ Number of stacks with differences: 1There we go, our S3 Bucket is there just like we expected! But we also get this BootstrapVersion as well, what is this?
Without getting into the weeds, this is just the CDK’s internal way of keeping track of the current CDK version difference between whats been deployed compared to what CLI version you’re using. That’s all, you should only see this the first time you deploy a stack or when the CDK version has changed.
Great, this looks good, now lets deploy it!
cdk deploy dev/appAssuming it completes successfully, you should see output like this:
✨ Synthesis time: 2.3s
dev-app: start: Building dev-app Templatedev-app: success: Built dev-app Templatedev-app: start: Publishing dev-app Template (************-us-east-1-661ffe41)dev-app: success: Published dev-app Template (************-us-east-1-661ffe41)dev/app (dev-app): deploying... [1/1]dev-app: creating CloudFormation changeset...
✅ dev/app (dev-app)
✨ Deployment time: 30.83s
Stack ARN:arn:aws:cloudformation:us-east-1:************:stack/dev-app/bc473560-da44-11f0-925f-06fbb7992bc3
✨ Total time: 33.13sAnd that’s it! If we go to S3, we should now see our new bucket that we just deployed!
We can also go into CloudFormation and see our deployed stack there as well:
Bonus: Vitest Setup
I mentioned Vitest earlier in this post, so let’s quickly swap out Jest for Vitest for our unit testing.
First thing, lets rip out Jest:
npm uninstall jest @types/jest ts-jestrm -rf jest.config.js test/*And install Vitest:
npm install -D vitest @vitest/coverage-v8Now we can go ahead and update our package.json
{ "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest", "test": "vitest run", "cdk": "cdk" },}Next we need to create a config file for Vitest:
touch vitest.config.tsimport { defineConfig } from 'vitest/config';
export default defineConfig({ appType: 'custom', test: { dir: 'test', coverage: { reporter: ['text', 'json', 'html'], include: ['src'], exclude: ['test', 'node_modules', 'dist'], }, },});And finally, let’s write our first unit test! For this test, we will simply verify that our S3 bucket is being created in the app stack.
touch test/app-stack.test.tsimport { describe, it } from 'vitest'import { App, Stage } from 'aws-cdk-lib'import { Template } from 'aws-cdk-lib/assertions'import { AppStack } from '../src/stacks/app-stack'
describe('app-stack', () => { it('should have an S3 bucket', () => { const app = new App(); const stage = new Stage(app, 'TestStage'); const stack = new AppStack(stage); const template = Template.fromStack(stack);
template.resourceCountIs('AWS::S3::Bucket', 1); template.hasResourceProperties('AWS::S3::Bucket', { BucketName: 'bcdk-example-bucket-123456', VersioningConfiguration: { Status: 'Enabled' } }); });});Now we can run our tests:
npm run testAnd we should see something like:
> my-cdk-project@0.1.0 test> vitest run
RUN v4.0.15 /my-cdk-project
✓ test/app-stack.test.ts (1 test) 68ms ✓ app-stack (1) ✓ should have an S3 bucket 67ms
Test Files 1 passed (1) Tests 1 passed (1) Start at 23:26:13 Duration 421ms (transform 25ms, setup 0ms, import 272ms, tests 68ms, environment 0ms)And that’s it! You’re now all setup to start building with the AWS CDK using TypeScript!
Want to skip all that and just grab the code? It’s available on GitHub!
https://github.com/brandonburrus/buildcdk-examples/tree/getting-started-with-aws-cdk