AWS CDK TypeScript
Overview
Use this skill to build AWS infrastructure in TypeScript with reusable constructs, safe defaults, and a validation-first delivery loop.
When to Use
Use this skill when:
-
Creating or refactoring a CDK app, stack, or reusable construct in TypeScript
-
Choosing between L1, L2, and L3 constructs
-
Building serverless, networking, or security-focused AWS infrastructure
-
Wiring multi-stack applications and environment-aware deployments
-
Validating infrastructure changes with cdk synth , tests, cdk diff , and cdk deploy
Instructions
- Project Initialization
Create a new CDK app
npx cdk init app --language typescript
Project structure
my-cdk-app/ ├── bin/ │ └── my-cdk-app.ts # App entry point (instantiates stacks) ├── lib/ │ └── my-cdk-app-stack.ts # Stack definition ├── test/ │ └── my-cdk-app.test.ts # Tests ├── cdk.json # CDK configuration ├── tsconfig.json └── package.json
- Core Architecture
import { App, Stack, StackProps, CfnOutput, RemovalPolicy } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as s3 from 'aws-cdk-lib/aws-s3';
// Define a reusable stack class StorageStack extends Stack { public readonly bucketArn: string;
constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
const bucket = new s3.Bucket(this, 'DataBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
removalPolicy: RemovalPolicy.RETAIN,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});
this.bucketArn = bucket.bucketArn;
new CfnOutput(this, 'BucketName', { value: bucket.bucketName });
} }
// App entry point const app = new App();
new StorageStack(app, 'DevStorage', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'us-east-1' }, tags: { Environment: 'dev' }, });
new StorageStack(app, 'ProdStorage', { env: { account: '123456789012', region: 'eu-west-1' }, tags: { Environment: 'prod' }, terminationProtection: true, });
app.synth();
- Construct Levels
Level Description Use When
L1 (Cfn* ) Direct CloudFormation mapping, full control Need properties not exposed by L2
L2 Curated with sensible defaults and helper methods Standard resource provisioning (recommended)
L3 (Patterns) Multi-resource architectures Common patterns like LambdaRestApi
// L1 — Raw CloudFormation new s3.CfnBucket(this, 'L1Bucket', { bucketName: 'my-l1-bucket' });
// L2 — Sensible defaults + grant helpers const bucket = new s3.Bucket(this, 'L2Bucket', { versioned: true }); bucket.grantRead(myLambda);
// L3 — Multi-resource pattern new apigateway.LambdaRestApi(this, 'Api', { handler: myLambda });
- CDK Lifecycle Commands
cdk synth # Synthesize CloudFormation template cdk diff # Compare deployed vs local changes cdk deploy # Deploy stack(s) to AWS cdk deploy --all # Deploy all stacks cdk destroy # Tear down stack(s) cdk ls # List all stacks in the app cdk doctor # Check environment setup
- Recommended Delivery Loop
Model the stack
-
Start with L2 constructs and extract repeated logic into custom constructs.
Run cdk synth
-
Checkpoint: synthesis succeeds with no missing imports, invalid props, missing context, or unresolved references.
-
If it fails: fix the construct configuration or context values, then rerun cdk synth .
Run infrastructure tests
-
Checkpoint: assertions cover IAM scope, stateful resources, and critical outputs.
-
If tests fail: update the stack or test expectations, then rerun the test suite.
Run cdk diff
-
Checkpoint: review IAM broadening, resource replacement, export changes, and deletes on stateful resources.
-
If the diff is risky: adjust names, dependencies, or RemovalPolicy , then rerun cdk diff .
Run cdk deploy
-
Checkpoint: the stack reaches CREATE_COMPLETE or UPDATE_COMPLETE .
-
If deploy fails: inspect CloudFormation events, fix quotas, permissions, export conflicts, or bootstrap issues, then retry cdk deploy .
Verify runtime outcomes
- Confirm stack outputs, endpoints, alarms, and integrations behave as expected before moving on.
- Cross-Stack References
// Stack A exports a value class NetworkStack extends Stack { public readonly vpc: ec2.Vpc; constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); this.vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 2 }); } }
// Stack B imports it via props interface AppStackProps extends StackProps { vpc: ec2.Vpc; } class AppStack extends Stack { constructor(scope: Construct, id: string, props: AppStackProps) { super(scope, id, props); new lambda.Function(this, 'Fn', { runtime: lambda.Runtime.NODEJS_20_X, handler: 'index.handler', code: lambda.Code.fromAsset('lambda'), vpc: props.vpc, }); } }
// Wire them together const network = new NetworkStack(app, 'Network'); new AppStack(app, 'App', { vpc: network.vpc });
Examples
Example 1: Serverless API
import * as cdk from 'aws-cdk-lib'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as apigateway from 'aws-cdk-lib/aws-apigateway'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
class ServerlessApiStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
const table = new dynamodb.Table(this, 'Items', {
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const fn = new lambda.Function(this, 'Handler', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: { TABLE_NAME: table.tableName },
});
table.grantReadWriteData(fn);
new apigateway.LambdaRestApi(this, 'Api', { handler: fn });
} }
Example 2: CDK Assertion Test
import { Template } from 'aws-cdk-lib/assertions'; import { App } from 'aws-cdk-lib'; import { ServerlessApiStack } from '../lib/serverless-api-stack';
test('creates DynamoDB table with PAY_PER_REQUEST', () => { const app = new App(); const stack = new ServerlessApiStack(app, 'TestStack'); const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::DynamoDB::Table', { BillingMode: 'PAY_PER_REQUEST', });
template.resourceCountIs('AWS::Lambda::Function', 1); });
Best Practices
-
One concern per stack — Separate network, compute, storage, and monitoring.
-
Prefer L2 constructs — Drop to Cfn* only when you need unsupported properties.
-
Set explicit environments — Pass env with account and region; avoid implicit production targets.
-
Use grant helpers — Prefer .grant*() over handwritten IAM where possible.
-
Review the diff before deploy — Treat IAM expansion, replacement, and deletes as mandatory checkpoints.
-
Test infrastructure — Cover critical resources with fine-grained assertions.
-
Avoid hardcoded values — Use context, parameters, or environment variables.
-
Use the right RemovalPolicy — RETAIN for production data, DESTROY only for disposable environments.
Constraints and Warnings
-
CloudFormation limits — Max 500 resources per stack; split large apps into multiple stacks
-
Synthesis is not deployment — cdk synth only generates templates; cdk deploy applies changes
-
Cross-stack references create CloudFormation exports; removing them requires careful ordering
-
Stateful resources (RDS, DynamoDB, S3 with data) — Always set removalPolicy: RETAIN in production
-
Bootstrap required — Run cdk bootstrap once per account/region before first deploy
-
Asset bundling — Lambda code and Docker images are uploaded to the CDK bootstrap bucket
References
Detailed implementation guides are available in the references/ directory:
-
Core Concepts — App lifecycle, stacks, constructs, environments, assets
-
Serverless Patterns — Lambda, API Gateway, DynamoDB, S3 events, Step Functions
-
Networking & VPC — VPC design, subnets, NAT, security groups, VPC endpoints
-
Security Hardening — IAM, KMS, Secrets Manager, WAF, compliance
-
Testing Strategies — Assertions, snapshots, integration tests, CDK Nag