IaC with CDK
Hub › AWS › Advanced › IaC with CDK
Goal
After this page you will have an AWS CDK (TypeScript) application that defines the DynamoDB table, Lambda function, and API Gateway entirely in code, and you will deploy it with a single cdk deploy command.
Prerequisites
- Lambda authorizer
- Node.js 20, AWS CLI configured.
Why CDK
So far you have created every resource through the AWS CLI or console. That works for learning, but it is not repeatable. CDK lets you define all infrastructure as TypeScript code, review it, version it in git, and deploy it with one command. Benefits:
- Self-documenting — the code is the architecture.
- Repeatable — same stack every time, no click-ops.
- Diffable —
cdk diffshows what will change before you deploy. - Destroyable —
cdk destroytears everything down.
Bootstrap
CDK needs a bootstrap stack (S3 bucket + IAM roles) in the target account. Run this once per account/region:
# PLACEHOLDER — use your own account ID.
npx aws-cdk bootstrap aws://123456789012/us-east-1Set up the project
mkdir items-cdk && cd items-cdk
npm init -y
npm install aws-cdk-lib constructs @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
npx aws-cdk init app --language typescriptThe CDK stack
Replace lib/items-cdk-stack.ts:
import { Stack, StackProps } from "aws-cdk-lib";
import { Table, AttributeType, BillingMode } from "aws-cdk-lib/aws-dynamodb";
import { Function, Runtime, Code } from "aws-cdk-lib/aws-lambda";
import { HttpApi, HttpMethod, HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2";
import { Construct } from "constructs";
export class ItemsCdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const table = new Table(this, "ItemsTable", {
tableName: "items",
partitionKey: { name: "pk", type: AttributeType.STRING },
billingMode: BillingMode.PAY_PER_REQUEST,
});
const handler = new Function(this, "ItemsHandler", {
runtime: Runtime.NODEJS_20_X,
code: Code.fromAsset("./lambda"),
handler: "index.handler",
environment: { TABLE_NAME: table.tableName },
});
table.grantReadWriteData(handler);
const api = new HttpApi(this, "ItemsApi", {
apiName: "items-api",
});
const integration = new HttpLambdaIntegration("ItemsIntegration", handler);
api.addRoutes({
path: "/items",
methods: [HttpMethod.GET, HttpMethod.POST],
integration,
});
// Outputs — see page 07 for cross-stack references.
new CfnOutput(this, "ApiUrl", { value: api.url! });
new CfnOutput(this, "TableName", { value: table.tableName });
new CfnOutput(this, "FunctionArn", { value: handler.functionArn });
}
}Lambda source
Create lambda/index.mjs:
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, ScanCommand, PutCommand } from "@aws-sdk/lib-dynamodb";
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME || "items";
export const handler = async (event) => {
try {
if (event.requestContext?.http?.method === "POST") {
const body = JSON.parse(event.body);
const item = {
pk: body.id?.toString() || crypto.randomUUID(),
name: body.name || "untitled",
createdAt: new Date().toISOString(),
};
await docClient.send(new PutCommand({ TableName: TABLE_NAME, Item: item }));
return { statusCode: 201, headers: { "Content-Type": "application/json" }, body: JSON.stringify(item) };
}
const result = await docClient.send(new ScanCommand({ TableName: TABLE_NAME }));
return {
statusCode: 200,
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
body: JSON.stringify(result.Items || []),
};
} catch (err) {
return { statusCode: 500, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: err.message }) };
}
};Deploy
# PLACEHOLDER — ensure your AWS CLI is configured for the target account.
npx aws-cdk deployCDK will synthesize a CloudFormation template, compare it with the current deployed stack, and prompt for confirmation. After approval it creates or updates the resources.
Checkpoint
After deployment completes, the terminal prints the ApiUrl output. Call it:
# PLACEHOLDER — replace with the output URL from cdk deploy.
curl https://abcd1234.execute-api.us-east-1.amazonaws.com/itemsExpected: 200 with the items in the table (or [] if the table is empty).
Next → SAM and local testing