AWS CDK: Infrastructure as Code with TypeScript

AWS Cloud Development Kit (CDK) enables defining infrastructure using familiar programming languages—TypeScript, Python, Java, C#, and Go. Instead of writing YAML/JSON, you use constructs, loops, conditionals, and functions. CDK synthesizes to CloudFormation, providing the best of imperative programming with declarative deployment. This guide covers CDK patterns, construct levels, and best practices for production deployments.

CDK Architecture

flowchart TB
    Code["CDK App (TypeScript)"] --> Synth["cdk synth"]
    Synth --> CFN["CloudFormation Template"]
    CFN --> Deploy["cdk deploy"]
    Deploy --> AWS["AWS Resources"]
    
    style Code fill:#E1F5FE,stroke:#0277BD
    style CFN fill:#FFF3E0,stroke:#E65100

Construct Levels

  • L1 (Cfn*): Direct CloudFormation resources
  • L2: Higher-level constructs with sensible defaults
  • L3 (Patterns): Complete solution patterns
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigw from 'aws-cdk-lib/aws-apigateway';

export class ApiStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    // L2 Construct: Lambda function with sensible defaults
    const handler = new lambda.Function(this, 'Handler', {
      runtime: lambda.Runtime.NODEJS_18_X,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'index.handler',
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
      environment: {
        TABLE_NAME: table.tableName,
      },
    });
    
    // L3 Construct: LambdaRestApi (complete pattern)
    const api = new apigw.LambdaRestApi(this, 'Api', {
      handler,
      proxy: false,
    });
    
    const orders = api.root.addResource('orders');
    orders.addMethod('GET');
    orders.addMethod('POST');
  }
}

Aspects for Cross-Cutting Concerns

import { IAspect, Stack, Tags } from 'aws-cdk-lib';
import { IConstruct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';

class BucketSecurityAspect implements IAspect {
  visit(node: IConstruct): void {
    if (node instanceof s3.Bucket) {
      // Enforce encryption on all buckets
      const bucket = node as s3.CfnBucket;
      bucket.addPropertyOverride('BucketEncryption', {
        ServerSideEncryptionConfiguration: [{
          ServerSideEncryptionByDefault: {
            SSEAlgorithm: 'aws:kms'
          }
        }]
      });
    }
  }
}

// Apply to entire app
cdk.Aspects.of(app).add(new BucketSecurityAspect());

Testing with CDK Assertions

import { Template } from 'aws-cdk-lib/assertions';
import * as cdk from 'aws-cdk-lib';
import { ApiStack } from '../lib/api-stack';

test('Lambda function created with correct memory', () => {
  const app = new cdk.App();
  const stack = new ApiStack(app, 'TestStack');
  const template = Template.fromStack(stack);
  
  template.hasResourceProperties('AWS::Lambda::Function', {
    MemorySize: 256,
    Runtime: 'nodejs18.x',
  });
});

test('API Gateway has expected routes', () => {
  template.resourceCountIs('AWS::ApiGateway::Method', 2);
});

Key Takeaways

  • CDK uses TypeScript/Python for infrastructure code
  • L2 constructs provide sensible defaults
  • L3 patterns implement complete solutions
  • Aspects enable cross-cutting security policies
  • CDK Assertions enable infrastructure unit tests
  • Synthesizes to CloudFormation for deployment

Discover more from C4: Container, Code, Cloud & Context

Subscribe to get the latest posts sent to your email.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.