CDK snippets
On this page, you can find code examples of CDK constructs with recommended configuration options.
AWS Lambda function
The following function creates a Node.js AWS Lambda using the CDK. It sets up bundling (ESM, minify, optional source maps), configures environment variables, denies CloudWatch logs to cut costs, and overrides the logical ID for OpenAPI compatibility. It's optimized for production use and external logging.
import { Stack, CfnElement } from 'aws-cdk-lib';
import { Function, Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as path from 'node:path';
function createLambdaFunction(
this: Stack,
id: string,
options: { entryPath: string; handler: string },
): Function {
const projectRootDir = path.join(import.meta.dirname, `../../`);
// Source maps help to debug stack traces, but also reduce performance.
// That's why we recommend to disable this on production.
// e.g. this.buildConfig.environment.toUpperCase() !== "PRD"
const enableSourceMaps = true;
const lambda = new NodejsFunction(this, id, {
runtime: Runtime.NODEJS_22_X,
memorySize: 1024,
entry: path.join(projectRootDir, options.entryPath),
handler: options.handler,
bundling: {
format: OutputFormat.ESM,
mainFields: ["module", "main"],
minify: true,
sourceMap: enableSourceMaps,
// enable this line if you want to exclude AWS SDK from the bundle,
// since AWS also provides the SDK in the Lambda runtime.
// Note that this comes with the risk of compatibility issues.
//externalModules: ["@aws-sdk/*"],
// enable if you find any issues with CommonJS modules that need "require()"
//banner: "import { createRequire } from 'module';const require = createRequire(import.meta.url);",
},
environment: {
NODE_OPTIONS: enableSourceMaps ? "--enable-source-maps" : "",
POWERTOOLS_SERVICE_NAME: "your-app-name",
APP_PROFILE: "AWS_DEV" // e.g. `AWS_${this.buildConfig.environment.toUpperCase()}`,
},
});
// This is done to be able to reference the logical name of Lambda functions in the OpenAPI specs.
// Otherwise, CDK adds a random suffix.
// Note: this is only needed when you're using API Gateway with OpenAPI.
(lambda.node.defaultChild as CfnElement).overrideLogicalId(id);
return lambda;
}
Disable CloudWatch logging for cost-saving
If you are using Datadog monitoring, you can disable CloudWatch logs:
import { IFunction } from 'aws-cdk-lib/aws-lambda';
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
/**
* For cost-saving: prevents the Lambda from creating a CloudWatch log group,
* since we already use Datadog for logging.
*/
function disableCloudWatchLogging(lambda: IFunction) {
lambda.addToRolePolicy(
new PolicyStatement({
effect: Effect.DENY,
actions: [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
resources: ["arn:aws:logs:*:*:*"],
}),
);
}
Amazon API Gateway
The following function creates an API Gateway from an OpenAPI spec, sets up logging, grants Lambda invoke permissions, and optionally adds WAF tagging and a custom domain with DNS and SSL.
import { Stack, RemovalPolicy, Tags, CfnOutput } from 'aws-cdk-lib';
import {
ApiDefinition,
CfnBasePathMapping,
EndpointType,
LogGroupLogDestination,
MethodLoggingLevel,
SpecRestApi,
DomainName
} from 'aws-cdk-lib/aws-apigateway';
import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
import { Effect, PolicyStatement, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { IFunction } from 'aws-cdk-lib/aws-lambda';
import { Certificate, CertificateValidation } from "aws-cdk-lib/aws-certificatemanager";
import { CnameRecord, IHostedZone } from "aws-cdk-lib/aws-route53";
function createApiGateway(
this: Stack,
id: string,
options: {
restApiName: string;
apiDefinition: ApiDefinition;
lambdaFunctions: IFunction[];
enableApiManagementACL: boolean;
customDomain?: {
subDomain: string;
};
},
): SpecRestApi {
// e.g this.buildConfig.environment.toUpperCase() == "DEV";
const isDevEnvironment = true;
const accessLogRemovalPolicy = isDevEnvironment
? RemovalPolicy.DESTROY
: RemovalPolicy.RETAIN;
const api = new SpecRestApi(this, id, {
restApiName: options.restApiName,
apiDefinition: options.apiDefinition,
deployOptions: {
loggingLevel: MethodLoggingLevel.ERROR,
accessLogDestination: new LogGroupLogDestination(
new LogGroup(this, `${id}Logs`, {
retention: RetentionDays.ONE_WEEK,
removalPolicy: accessLogRemovalPolicy,
}),
),
metricsEnabled: true,
},
disableExecuteApiEndpoint: options.customDomain !== undefined,
endpointTypes: [EndpointType.REGIONAL],
cloudWatchRole: true,
});
const apiGatewayServicePrincipal = new ServicePrincipal(
"apigateway.amazonaws.com",
{
conditions: {
ArnLike: {
"aws:SourceArn": api.arnForExecuteApi(),
},
},
},
);
options.lambdaFunctions.forEach((it) =>
it.grantInvoke(apiGatewayServicePrincipal),
);
if (options.enableApiManagementACL) {
Tags.of(api).add("FMIncludeAPIMgtWebACL", "true");
}
if (options.customDomain) {
// can be retrieved from the LZ, e.g. using the util further down this page.
const accountDomainName = 'todo'
const accountHostedZone: IHostedZone = 'todo' as any
const customDomainName = `${options.customDomain.subDomain}.${accountDomainName}`;
const apiGatewayCertificate = new Certificate(
this,
`${id}DomainCertificate`,
{
domainName: customDomainName,
validation: CertificateValidation.fromDns(accountHostedZone),
},
);
const customDomain = new DomainName(this, `${id}CustomDomain`, {
domainName: customDomainName,
certificate: apiGatewayCertificate,
endpointType: EndpointType.REGIONAL,
mapping: api,
});
new CnameRecord(this, `${id}CnameRecord`, {
zone: accountHostedZone,
recordName: options.customDomain.subDomain,
domainName: customDomain.domainNameAliasDomainName,
});
new CfnOutput(this, `${id}Url`, {
key: `${id}Url`,
value: `https://${customDomainName}`,
});
}
return api;
}
Amazon DynamoDB
The following function creates a DynamoDB table with point-in-time recovery enabled:
import { Stack, RemovalPolicy } from 'aws-cdk-lib';
import { Table, Attribute, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
import { AccountPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
function createDynamoDbTable(
this: Stack,
id: string,
options: { partitionKey: Attribute; sortKey?: Attribute },
) {
return new Table(this, id, {
partitionKey: options.partitionKey,
sortKey: options.sortKey,
billingMode: BillingMode.PAY_PER_REQUEST,
pointInTimeRecoverySpecification: {
pointInTimeRecoveryEnabled: true,
},
removalPolicy: RemovalPolicy.RETAIN // or RemovalPolicy.DESTROY for "DEV
});
}
Amazon SNS topic
The following function creates an SNS topic, and arranges permissions for other AWS accounts to subscribe to it:
import { Stack } from 'aws-cdk-lib';
import { Topic } from 'aws-cdk-lib/aws-sns';
import { SqsSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';
import { AccountPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { IFunction } from 'aws-cdk-lib/aws-lambda';
function createSnsTopic(
this: Stack,
id: string,
options: { grantAwsAccountIds: string[]; publishingFunctions: IFunction[] }
) {
const topic = new Topic(this, id, {
displayName: id,
topicName: id,
});
topic.addToResourcePolicy(
new PolicyStatement({
effect: Effect.ALLOW,
actions: ['SNS:Subscribe'],
principals: options.grantAwsAccountIds.map((awsAccountId) => new AccountPrincipal(awsAccountId)),
resources: [topic.topicArn],
}),
);
options.publishingFunctions.forEach((lambdaFunction) => topic.grantPublish(lambdaFunction));
return topic;
}
Amazon SQS queue
This function creates an SQS queue and subscribes to an SNS topic:
import { Stack, Duration } from 'aws-cdk-lib';
import { ITopic } from 'aws-cdk-lib/aws-sns';
import { Queue } from 'aws-cdk-lib/aws-sqs';
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
import { SqsSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';
import { IFunction } from 'aws-cdk-lib/aws-lambda';
function createSqsQueue(
this: Stack,
id: string,
options: { lambdaFunction: IFunction; topic: ITopic }
) {
const deadLetterQueue = new Queue(this, `${id}Dlq`, {
retentionPeriod: Duration.days(14),
});
const queue = new Queue(this, id, {
visibilityTimeout: Duration.seconds(60),
receiveMessageWaitTime: Duration.seconds(0),
deadLetterQueue: {
queue: deadLetterQueue,
maxReceiveCount: 1, // Maximum number of times a message can be received before being sent to DLQ
},
});
options.lambdaFunction.addEventSource(
new SqsEventSource(queue, {
maxBatchingWindow: Duration.seconds(15),
reportBatchItemFailures: true,
}),
);
options.topic.addSubscription(new SqsSubscription(queue));
return queue;
}
Landing Zone (LZ) utility
The following utility can be used to retrieve shared resources from the Landing Zone. More information on the AWS Landing Zone can be found on Confluence.
import { type IStringParameter, StringParameter } from "aws-cdk-lib/aws-ssm";
import { Construct } from "constructs";
import { HostedZone, type IHostedZone } from "aws-cdk-lib/aws-route53";
/**
* Utility to interact with the AWS Landing Zone
*
* @see https://ns-topaas.atlassian.net/wiki/spaces/NSCAWS/pages/455770915/NS+Cloud+AWS+Landing+Zone
*/
export class LandingZoneConstruct extends Construct {
/**
* Shared domain name of the AWS account
*/
public accountDomainName: IStringParameter;
/**
* Shared hosted zone id of the AWS account
*/
public accountHostedZoneId: IStringParameter;
/**
* Shared hosted zone of the AWS account
*/
public accountHostedZone: IHostedZone;
constructor(scope: Construct, id: string) {
super(scope, id);
this.accountDomainName = StringParameter.fromStringParameterAttributes(scope, "AccountDomainName", {
parameterName: "/LZ/DNS/PublicHostedZone/DomainName",
});
this.accountHostedZoneId = StringParameter.fromStringParameterName(
scope,
"AccountHostedZoneId",
"/LZ/DNS/PublicHostedZone/ID",
);
this.accountHostedZone = HostedZone.fromHostedZoneAttributes(this, "AccountHostedZone", {
hostedZoneId: this.accountHostedZoneId.stringValue,
zoneName: this.accountDomainName.stringValue,
});
}
}