Skip to content
C Codeloom
AWS

AWS CDK: Infrastructure as TypeScript

Use the AWS Cloud Development Kit to define cloud infrastructure in TypeScript with constructs, stacks, and CloudFormation as the deploy engine.

·6 min read · By Yash Kesharwani
Intermediate 10 min read

What you'll learn

  • What CDK is and how it relates to CloudFormation
  • How constructs, stacks, and apps fit together
  • How to bootstrap and deploy your first stack
  • How to share resources across stacks
  • How to choose between CDK and Terraform

Prerequisites

  • Comfortable with TypeScript
  • Familiar with AWS basics — see What is AWS

The AWS Cloud Development Kit lets you define cloud infrastructure in a real programming language. You write TypeScript classes, the CDK synthesizes a CloudFormation template, and CloudFormation provisions the resources. You get loops, conditionals, types, and IDE autocomplete in your infrastructure. You also keep CloudFormation’s drift detection and rollbacks.

Install and project setup

CDK ships as an npm package. Create a new project with the CLI.

npm install -g aws-cdk
mkdir codeloom-infra && cd codeloom-infra
cdk init app --language typescript

The scaffold gives you bin/codeloom-infra.ts (the app entry point), lib/codeloom-infra-stack.ts (your first stack), cdk.json (settings), and the usual TypeScript files.

The three building blocks

CDK has a clear hierarchy:

  • App — the root, containing one or more stacks.
  • Stack — maps one-to-one to a CloudFormation stack. The unit of deploy.
  • Construct — a reusable component, anything from a single S3 bucket to a full microservice.

Constructs are the heart of CDK. They come in three levels:

  • L1 — direct CloudFormation. Classes start with Cfn. Verbose, no opinions.
  • L2 — friendlier wrappers with sensible defaults. Most code uses these.
  • L3 — patterns that combine several L2 constructs into one solution.

Your first stack

import { Stack, StackProps, RemovalPolicy } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class CodeloomInfraStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'Uploads', {
      versioned: true,
      removalPolicy: RemovalPolicy.RETAIN,
      encryption: s3.BucketEncryption.S3_MANAGED,
    });

    const fn = new lambda.Function(this, 'Processor', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda'),
      environment: {
        BUCKET: bucket.bucketName,
      },
    });

    bucket.grantReadWrite(fn);
  }
}

Two things to notice. First, grantReadWrite writes the IAM policy for you. You do not enumerate actions or resources. Second, passing bucket.bucketName as an environment variable creates an implicit dependency — CDK orders the deploy so the bucket exists first.

Bootstrap and deploy

Before the first deploy in an account or region, run cdk bootstrap. It creates an S3 bucket and IAM roles CDK uses to upload assets.

cdk bootstrap aws://123456789012/us-east-1
cdk diff
cdk deploy
  • cdk diff shows what would change.
  • cdk deploy does it.
  • cdk synth prints the synthesized CloudFormation template.

Treat diff the way Terraform users treat plan. Read it before every deploy.

Multi-stack apps

Split a system across stacks when the lifecycles differ. A long-lived data layer should not redeploy every time the API changes.

const app = new cdk.App();

const data = new DataStack(app, 'Data', { env: prodEnv });
new ApiStack(app, 'Api', {
  env: prodEnv,
  table: data.table,
});

Pass resources between stacks as constructor props. CDK exports the value from one stack and imports it into the other via CloudFormation outputs, automatically.

Reusable constructs

Build a construct when you find yourself wiring the same group of resources twice.

import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';

export interface SiteProps {
  domain: string;
}

export class StaticSite extends Construct {
  constructor(scope: Construct, id: string, props: SiteProps) {
    super(scope, id);

    const bucket = new s3.Bucket(this, 'Bucket', {
      websiteIndexDocument: 'index.html',
    });

    new cloudfront.Distribution(this, 'Cdn', {
      defaultBehavior: { origin: new cloudfront.S3Origin(bucket) },
    });
  }
}

Stacks use it like a built-in:

new StaticSite(this, 'Marketing', { domain: 'codeloom.dev' });

Environment configuration

The same code can deploy to dev, staging, and prod. Use StackProps.env and pass context.

const envs = {
  dev: { account: '111', region: 'us-east-1' },
  prod: { account: '222', region: 'us-east-1' },
};

for (const [name, env] of Object.entries(envs)) {
  new ApiStack(app, `Api-${name}`, { env });
}

Do not put environment-specific values in the construct itself. Pass them in.

Testing

CDK ships an assertions module so you can write unit tests against the synthesized template.

import { Template } from 'aws-cdk-lib/assertions';

test('bucket is encrypted', () => {
  const app = new cdk.App();
  const stack = new CodeloomInfraStack(app, 'Test');
  const tpl = Template.fromStack(stack);

  tpl.hasResourceProperties('AWS::S3::Bucket', {
    BucketEncryption: {
      ServerSideEncryptionConfiguration: [
        { ServerSideEncryptionByDefault: { SSEAlgorithm: 'AES256' } },
      ],
    },
  });
});

Snapshot tests catch unintended changes. Property assertions enforce policy.

CDK vs Terraform

Both define infrastructure as code. The split:

  • CDK uses real code and is AWS-first (other providers exist but are less mature). CloudFormation is the deploy engine.
  • Terraform uses HCL and supports every major cloud. State is the ledger.

If your shop is all-in on AWS and you want loops and abstractions in a real language, CDK is excellent. If you need multi-cloud or already have Terraform skills, stay there. Pick the one your team will maintain.

CI integration

CDK pipelines hook into CI/CD naturally. A typical pipeline runs npm ci, cdk synth, posts the diff as a PR comment, and on merge runs cdk deploy with a long-lived role. The CDK Pipelines construct codifies that pattern using AWS CodePipeline.

Pitfalls

  • Logical IDs change when you move constructs around the tree, causing CloudFormation to replace resources. Use explicit IDs or overrideLogicalId when refactoring stateful resources.
  • Leaving removalPolicy at the default destroys data on stack deletion. Set RETAIN for buckets and tables in production.
  • Mixing many stacks across regions in one app. Synthesis works, but deploys get slow and dependencies become opaque.

Wrap up

CDK takes the strongest part of CloudFormation (managed state, drift detection, deep AWS integration) and pairs it with a programming language. You write small TypeScript classes, the CLI synthesizes templates, and CloudFormation deploys them. With a good construct library and a tight CI loop, infrastructure becomes just another module in your app — reviewed in pull requests, tested, and deployed automatically.