AWS CDK allows you to quickly build scalable, cost-effective, and reliable applications in the cloud. Learn how you can use it for your next project.

Kentaro Wakayama
14 July 2022

Creating and managing cloud infrastructure manually via a web console is a slow and unreliable approach, as it’s easy to make configuration mistakes. That’s why developers have always tried to automate these processes, making them effective, transparent, and easy to replicate or revert.
In this article, you will learn about AWS Cloud Development Kit (CDK), an open-source software development framework for defining cloud application resources using familiar programming languages.
Before diving into AWS CDK, let’s talk about AWS CloudFormation, which is used under AWS CDK’s hood. In short, CloudFormation allows you to provision infrastructures of any complexity using templates written in JSON or YAML formats. AWS consumes the template file, creates all the services described, detects changes, and applies them accordingly or makes a rollback if something goes wrong.
AWS CDK is a tool that can transform TypeScript (or JavaScript, Python, or even Java) code into a CloudFormation template, prepare any other artifacts your application needs, and deploy everything to a selected AWS account. To be precise, all the resources are described as special TypeScript classes known as constructs, which may inherit from, extend, or nest other constructs that are predefined by the AWS CDK development team, as well as by your own.
AWS CDK constructs are the main building blocks of AWS CDK applications. Each block represents an AWS resource or a set of resources. There are three levels of AWS CDK constructs. First level (L1) and second level (L2) constructs are written by the AWS CDK team and are available out of the box. Third level (L3) constructs are usually more complex classes written by you or a third party. They often consist of additional constructs that form a set of resources related to a single logical part. For example, an L3 construct can consist of an S3 bucket and AWS Lambda functions that are triggered when a new object is uploaded or deleted from the bucket.
Let’s say you need a simple Amazon S3 bucket with public access and versioning enabled. Here is how it would look in a raw CloudFormation template:
"Resources": {
"S3Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "my-bucket-name-1",
"AccessControl": "PublicRead",
"VersioningConfiguration": {
"Status": "Enabled"
}
}
}
}
Now look at how the same resource can be described via an AWS CDK L1 construct:
import * as s3 from '@aws-cdk/aws-s3';
// ...
const myBucket = new s3.CfnBucket(this, 'my-cfn-bucket', {
bucketName: 'my-bucket-name-1',
accessControl: 'PublicRead',
versioningConfiguration: {
status: 'Enabled'
}
});
As you can see, the CloudFormation template looks very similar to the L1 construct code. The properties are the same, except for the CamelCase syntax. That’s the main thing about L1 constructs: Their configuration maps 1:1 with CloudFormation, and there are no methods or properties exposed by the created instance.
Here is an AWS CDK L2 construct:
import * as s3 from '@aws-cdk/aws-s3';
// ...
const myBucket = new s3.Bucket(this, 'my-bucket', {
bucketName: 'my-bucket-name-1',
publicReadAccess: true,
versioning: true
});
Constructs like these add more abstraction; the configuration properties are a bit more readable; and they help you write less code while implementing common resource behaviors. L2 constructs are the most commonly used, as they also provide convenient methods and useful properties for interacting with other constructs.
For example, you can grant read/write permissions to any other construct in just one line:
const myLambda = lambda.Function(/* config */);
const myBucket = s3.Bucket(/* config */);
myBucket.grantReadtWrite(myLambda);
You may be wondering what the real advantages of using AWS CDK are, especially since it looks like you still need to write code to describe your infrastructure. Or maybe you already know CloudFormation, or another tool built on top of it, and don’t see any reason to learn something new to reinvent the wheel. However, there are some awesome benefits to AWS CDK, which definitely make it worth a try.
AWS CDK allows you to write independent components, which are easy to publish and share among other developers. Besides the construct prepared by the AWS CDK team, there are plenty of ready-to-use building blocks available on GitHub or in the Construct Hub. Once a construct is written, it can be easily reused in other parts of the AWS CDK application or in another app—without redundantly copy/pasting a similar configuration. Write less and do more.
With AWS CDK, you can use all programming features—such as variables, loops, functions, conditional statements, encapsulation, inheritance, and more—when you write software code instead of a JSON or YML template. Moreover, you can use your favorite IDE more effectively.
As mentioned above, your infrastructure is software code. This means you can now cover it with tests of any form, just as you do for other applications.
Now that you understand the main concepts of AWS CDK, you’re ready to reap the benefits. In order to start coding, you will need an active AWS account and Node.js installed on your machine.
If you have ever used AWS CLI, you probably already know how to get and set the security credentials you need in order to interact with AWS services from your local machine. If you’re doing this for the first time now, make sure to use the AWS guide on basic configuration.
Once you’re ready, install the AWS CDK CLI. Open the terminal and run the following command to start the installation:
$ npm install -g aws-cdk@2.x
Next, bootstrap AWS CDK via the below command (replace the placeholders with the corresponding values):
$ cdk bootstrap aws://ACCOUNT_NUMBER/REGION
Great! You are now ready to start developing your first AWS CDK app.

In this tutorial, you will build a project consisting of a DynamoDB table, a Lambda function for performing basic CRUD operations, and an API gateway endpoint. Despite its simplicity, this example will cover all the basics of working with AWS CDK.
First, create the folder my-cdk-app. Then navigate to it and run the following in the terminal:
$ cdk init app --language typescript
This is everything you need in order to start coding your infrastructure. Before you begin, you can explore the files and folders created in the project. The bin folder is basically an entry point for your application, and the lib folder will be used to describe the resources of the application’s AWS stack.

Now open the lib/my-cdk-app-stack.ts file. You will see the MyCdkAppStack class with an empty constructor. This is the place to define AWS resources. For simplicity, in this example, all the resources will be listed in this file. However, in the real project, you might want to list the resources in different files and group them in different folders and then only import high-level constructs.
On top of the file, add the following lines in order to import the necessary modules:
import * as cdk from '@aws-cdk/core';
import {
aws_lambda as lambda,
aws_dynamodb as dynamo,
aws_apigateway as agw,
} from 'aws-cdk-lib'
In the constructor, right after the super() statement, add the following lines:
const productsTable = new dynamo.Table(this, 'db-table', {
partitionKey: {
name: "id",
type: dynamo.AttributeType.STRING
},
removalPolicy: cdk.RemovalPolicy.DESTROY
});
Note that the removalPolicy attribute is not necessary, as it means that this table needs to be deleted when you delete this stack in the end. Also note that this construct (and every following construct) always takes three parameters: context (usually this), construct id (which should be unique in the scope of the current context), and the configuration object.
In the same place, right after you defined the DynamoDB table-related code, write this:
const crudLambda = new lambda.Function(this, 'crud-lambda', {
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('code/crud'),
environment: {
PRODUCTS_TABLE: productsTable.tableName
}
});
productsTable.grantReadWriteData(crudLambda); // give permissions
Most of the attributes here should be self-explanatory, except the code one. There are multiple ways of telling AWS CDK where and how to get different assets, like source code or other artifacts needed by AWS resources. In this case, you can use the Code.fromAsset(...) expression to tell AWS CDK that the code of this Lambda function is located in the code/crud folder.
In the root of the project, create a folder named code and another folder named crud inside of it. Finally, in the crud folder, create a file index.js with the following code:
const AWS = require("aws-sdk");
const dynamo = new AWS.DynamoDB.DocumentClient();
const productsTable = process.env.PRODUCTS_TABLE;
const response = (code, data = {}) => ({
statusCode: code,
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
});
module.exports.handler = async (event) => {
const { httpMethod, body } = event;
try {
if (httpMethod === "GET") {
const data = await dynamo.scan({ TableName: productsTable }).promise();
return response(200, data);
}
if (httpMethod === "POST") {
await dynamo.put({ TableName: productsTable, Item: JSON.parse(body)}).promise();
return response(200);
}
} catch (err) {
return response(500, { msg: err.message })
}
}
This code is not important for this example, so don’t focus on it. However, it is worth noting that this function is written directly in JavaScript for simplicity and so that you can skip transpiling the steps that would be required if it was in TypeScript. There is also another high-level construct designed for creating Lambda functions, with automatic transpiling and bundling of TypeScript or Javascript code, before deploying the app.
The last part of the code is dedicated to the API gateway and connecting it to the Lambda function. Add the following code again in the lib/my-cdk-app-stack.ts file in the bottom of the constructor:
const api = new agw.LambdaRestApi(this, 'api', {
handler: crudLambda
});
Elegant, isn’t it? Just imagine the amount of CloudFormation code required to achieve the same result!
If you want to take a look at the generated CloudFormation template without deploying an application, run this code:
$ cdk synth
It will output the huge text in YAML format. In order to deploy the application to your AWS environment, run this:
$ cdk deploy
Note that AWS CDK will ask you to approve the deployment. The deployment process takes up to a few minutes, and then you will see your stack ARN and the URL of your API endpoint. Congratulations, you’ve just created and deployed AWS infrastructure using AWS CDK!
If you want to test the Lambda function, just send a couple of POST and GET requests to the endpoint URL. First, create an item:
$ curl -X POST -H "Content-Type: application/json" -d '{"id":"a1", "name":"boots"}' ENDPOINT_URL_HERE
Then, get a list of items from the table:
$ curl ENDPOINT_URL_HERE
You might also want to visit the AWS web console, navigate to the CloudFormation dashboard, and check out your stack there, as well as a list of all related resources.
Finally, when you are done and ready to remove the stack, simply run the below command and approve your intent:
$ cdk destroy
Today you learned the basics of AWS CDK, but it is much more powerful than this post’s example shows. For example, you can use CDK Pipelines to implement continuous integration and delivery of your applications, and to create separate stages. You should also know about creating custom resources, which can be very useful if your architectures include resources that are not a part of AWS. Finally, there are some really important best practices and approaches you should follow if you want to implement and manage reliable and maintainable infrastructure using AWS CDK.
If this article has inspired you to start using AWS CDK, remember that you don’t have to wait until you start a new project, as it supports step-by-step adoption with the possibility of importing existing CloudFormation templates. Happy coding!