Skip to content

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.

ts
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:

ts
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.

ts
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:

ts
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:

ts
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:

ts
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.

ts
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
,
}); } }