An opinionated base project template for any TypeScript app using CDK for deployment to AWS.
The AWS Cloud Development Kit (CDK) is a tool to help with deploying applications to AWS. From one of my articles:
CDK has become, in its short history, a very popular infrastructure-as-code tool. It's not too surprising why - it allows engineers to use richer programming languages to define infrastructure, rather than having to use JSON or YAML. With richer languages comes better tooling, better abstractions, and more.
While CDK is very powerful, it requires a fair amount of "ceremony" to get up and running. This is especially true when using CDK with TypeScript, which itself comes with an assortment of metadata files.
While there are other various tools and templates available for CDK apps (including the bundled tooling), this project is my (opinionated) take on a "bare-bones" project setup.
You can you use this template repo as the base for your own TypeScript + CDK applications.
The name of this project says "bare-bones", and I mean it! There's no testing, deployment automation ("CD"), no actual resources, no source code.
If you'd like an example with more functionality I also provide these template projects that are extensions of this one:
- Coffee Store V2 - Adds a Lambda Function resource; source code + build for the Lambda Function; unit + in-cloud integration tests
- Coffee Store Web Basic - Website hosting on AWS with CloudFront and S3
- Coffee Store Web Full - A further extension of Coffee Store Web Basic that is a real working demo of a production-ready website project, including TLS certificates, DNS hosting, Github Actions Workflows, multiple CDK environments (prod vs test vs dev)
To use this project your terminal must be setup correctly for the correct AWS acount and region, and your account and region in AWS must be bootstrapped for CDK. See the appendix for more details if you need help setting either of these up.
Further, Node and NPM should be correctly setup. This project currently assumes you are using Node 16, although you can likely change that for your own Node version if you're using something different, as long as it is supported by CDK. If your terminal isn't already configured for Node see the associated appendix later.
The terminal examples below assume a Bash / Zsh shell, or equivalent, with the leading
$
signifiying the terminal prompt. If you are using Windows you will need to adjust these commands if using a different type of terminal.
To deploy this application to AWS for the first time, run the following:
$ npm install && npm run deploy
If successful, the end result will look something like this:
✨ Deployment time: 18.19s
Stack ARN:
arn:aws:cloudformation:us-east-1:123456789012:stack/app-stack/12345678-1234-1234-1234-123456789ab
✨ Total time: 20.71s
The deployed application is the smallest possible CDK app - the only deployed resource is some CDK Metadata.
Once you've run
npm install
once in the directory you won't need to again
In situations where you may deploy the same app multiple times with different names to the same AWS account you will need to override the default stack name. This is common, for example, for development accounts.
To do this you can run the following:
$ npm run deploy -- --context stackName=my-app-stack
...
✅ AppStack (my-app-stack)
✨ Deployment time: 22.64s
Stack ARN:
arn:aws:cloudformation:us-east-1:123456789012:stack/my-app-stack/12345678-1234-1234-1234-123456789ab
✨ Total time: 25.23s
You can specify other parameters in the same way, e.g. to use hotswap deployment you can run the following:
$ npm run deploy -- --hotswap
...
⚠️ The --hotswap flag deliberately introduces CloudFormation drift to speed up deployments
⚠️ It should only be used for development - never use it for your production Stacks!
AppStack (app-stack): deploying...
To tear down the stack, run the following. IMPORTANT - if you haven't made any changes to the project this will delete
the default stack (app-stack
) in your Account + Region.
$ npm run cdk-destroy
...
Are you sure you want to delete: AppStack (y/n)? y
AppStack (app-stack): destroying...
✅ AppStack (app-stack): destroyed
If you want to teardown a stack with name that's not the default, you can add -- --context stackName=my-app-stack
, the same as with deploy
.
Another command in package.json is cdk-diff
, which runs just like deploy
and cdk-destroy
above.
You can also run the command cdk-command
, and set the command
flag to any valid cdk CLI command, e.g. the following is exactly the same as running cdk deploy
with a custom stack name:
$ npm run cdk-command --command=deploy -- --context stackName=my-app-stack
The point of using npm run cdk-command --command=deploy
vs npx cdk command
is to set to the cdk output directory in a consistent way.
To start with you might want to create your own Github repository based on using this one as a template. See Github's instructions here for creating from a template.
Since this is a "bare-bones" template you will need to extend it for your own needs. A few ideas to kick you off are as follows:
- The entrypoint for your CDK deployment is in app.ts .
- You will likely want to rename the
AppStack
class, the ID of the instantiatedAppStack
, and also change the default stack name (app-stack
) - You should start adding your resources to this file too
- You will likely want to rename the
- The "stack properties" in this template are just the environment and stack name, but you will likely want to extend those too.
- Consider adding a deployment script to the repo if you want to be able to deploy to different stack names in the same AWS account - this template always defaults the stack name.
For more concrete ideas, see the extensions to this project that I listed earlier.
As I've mentioned, this is my opinionated take on a starting point for a CDK app, and it deviates from the standard tooling and documentation from AWS. Here are a few places it changes from running npx cdk init app --language typescript
, and why:
The CDK CLI init
version has more dependencies and included scripts than this project. As I've mentioned already this project is deliberately bare-bones, solely providing enough for you to deploy a CDK application.
The CDK CLI init
version has a lengthy tsconfig.json file - my version is much shorter, and largely
defers to the recommended configuration from the @tsconfig/...
library.
All source code that is committed to the repo is in the /src directory, and any generated code is somewhere under build. So my template puts all CDK source code under /src/cdk (rather than /bin and /lib from the CDK CLI tooling) and the CDK output under /build/cdk.out, rather than /cdk.out.
Further my current feeling is that CDK source code and application source code are best separated, rather than combined into one tree, so in my own apps I typically have a /src/app, or equivalent, as a peer of /src/cdk.
In the CDK CLI init
version you get both a top-level Node script under /bin, and a file for the Stack class under /lib. I think separating these two concepts is (usually) unnecessarily complicated, at least to get started, and so in my version they are combined. If I cared about unit-testing for my top-level stack class in a particular app I would introduce such a separation.
Further I always define the Environment (AWS Account + region) for my stack. This will cause problems if the CDK launcher is run without an AWS environment, but I have found in my usage of CDK that I always actually do want an environment. In theory you can run without an AWS environment, but I've never found that particularly useful.
My reasoning for always setting the Environment is that I assume that pretty much the sole usage of the CDK code is when running cdk deploy
/ cdk watch
, and occasionally cdk diff
or cdk destroy
- in other words commands that actually interact with your AWS account in the cloud. I've never found it particularly useful (for example) to just run cdk synth
and use the generated CloudFormation separately.
First of all - cdk.json is not in the project root. This is because it means there's one fewer file in project root, which I think is A Good Thing, and it makes it easier to have repos with multiple, separate, CDK apps.
I think the cdk.json file generated by the CLI is far too complicated. For example I think for a new application it makes much more sense to the use the default settings for context
. Similarly, I think that cdk watch
is useful, however I don't build my CDK apps the way that the CDK CLI expects me to, so the watch
property from the default init
is also not helpful for me.
I add a couple of properties though. Firstly I add output
to point from the /src/cdk directory to /build/cdk.out (related to my Directory layout comments above). I also set requireApproval
to never
, because I think the default setting is too chatty, at least how for how I use CDK.
You must have a valid AWS profile pointing to your desired AWS account and region set up in your terminal environment.
To check your profile, run the following:
NB: this assumes you have the AWS CLI installed in your terminal.
$ aws sts get-caller-identity --query 'Account' --output text
123456789012
$ aws configure get region
us-east-1
In this example my profile is targeting AWS Account ID 123456789012
in the us-east-1
region.
If your profile is NOT setup correctly, see the documentation here.
The account must also have been bootstrapped for CDK. If your account is using the default bootstrap configuration it will be deployed to CloudFormation with the stack name CDKToolkit
.
In case you are uncertain then you can check from the terminal whether CDK has been bootstrapped:
$ aws cloudformation describe-stacks --stack-name CDKToolkit
This will return the stack details, or an error if the stack doesn't exist.
If your account isn't Bootstrapped for CDK see the following section.
If your AWS account is not already bootstrapped for CDK (see the previous section), you can use this project to perform bootstrapping.
IMPORTANT: - CDK Bootstrapping has security implications, and should not be performed on sensitive accounts without considering the ramifications. For more details about CDK Bootstrapping, see the AWS Documentation.
From the documentation:
Deploying AWS CDK apps into an AWS environment (a combination of an AWS account and region) requires that you provision resources the AWS CDK needs to perform the deployment. These resources include an Amazon S3 bucket for storing files and IAM roles that grant permissions needed to perform deployments. The process of provisioning these initial resources is called bootstrapping.
CDK Bootstrapping can be highly customized, but to use the default settings, using the version of CDK in this project, you can run the following (NOTE - this will use the AWS account and region setup in your profile, see AWS Profile above.)
$ npm install && npx cdk bootstrap --output build/cdk.out
This will take a couple of minutes, but the output should look something like this once complete:
✅ Environment aws://123456789012/us-east-1 bootstrapped.
Since this is a TypeScript Node application you need to have Node available in your terminal environment.
There are various ways of managing Node, but here is my current preferred method:
- Install NVM in my user-global environment
- Use NVM's shell integration to automatically load the correct version of Node according to the .nvmrc file
If you have questions related to this example please add a Github issue, or drop me a line at mike@symphonia.io . I'm also on Twitter at @mikebroberts .
- Move cdk.json to src/cdk directory. This is for a couple of reasons:
- One fewer file in project root, which I think is A Good Thing
- Makes it easier to have repos with multiple, separate, CDK apps
- Move
output
andrequireApproval
CDK settings from package.json to cdk.json- I hadn't read the docs enough to know they could be in cdk.json. Oops. This way is cleaner
- Add package-lock.json - these are specific dependency versions I've tested with