
Creating Lambdas can be painful. Specially when you have to keep updating them and integrate them with other AWS services. That’s where CDK comes to the rescue.
In this tutorial, we’ll be learning how to create an initialise a CDK project, how to write Lambdas using TypeScript, how to deploy Lambdas by using AWS CDK, and also add a function URL to test and invoke it easily.
Creating a CDK project
Open up your favourite terminal or Powershell (if you’re using Windows).
Create a directory / folder using the command mkdir hello-lambda
.
Go into your newly created directory using cd hello-lambda
.
Make sure you’ve npm and nodejs installed. If not, you can download it from nodejs.org.
Run npx cdk init app --language typescript
This will initialise a new CDK project in your current directory.
If your project directory is not empty, then it will fail to create a CDK project.

Understanding CDK file structure
Now that your CDK project has been created, you’ll see the following files:
├── bin
│  └── hello-lambda.ts
├── cdk.json
├── jest.config.js
├── lib
│  └── hello-lambda-stack.ts
├── package.json
├── package-lock.json
├── README.md
├── test
│  └── hello-lambda.test.ts
└── tsconfig.json
The bin/hello-lambda.ts
imports the lambda stack and can be configured to deploy multiple stacks into different regions.
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { HelloLambdaStack } from '../lib/hello-lambda-stack';
const app = new cdk.App();
new HelloLambdaStack(app, 'HelloLambdaStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */
/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
// env: { account: '123456789012', region: 'us-east-1' },
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
You will see we initialize a new stack using new HelloLambdaStack()
.
Every stack needs to have unique name eg.
HelloLambdaStack
, so you can’t create another stack with same id.
We will write our first Lambda stack in lib/hello-lambda-stack.ts
.
Open the CDK project directory in your favourite code editor (e.g. Neovim, VS Code).
You’ll see something like this.
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
export class HelloLambdaStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// The code that defines your stack goes here
// example resource
// const queue = new sqs.Queue(this, 'HelloLambdaQueue', {
// visibilityTimeout: cdk.Duration.seconds(300)
// });
}
}
Creating Lambda stack
First we need to import NodejsFunction
constructor to define a lambda function then we import enums for defining our OutputFormat
and Runtime
:
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction, OutputFormat } from "aws-cdk-lib/aws-lambda-nodejs";
Now using the NodejsFunction
constructor we create a new Lambda having id of hello-lambda
:
new NodejsFunction(this, "hello-lambda", {
// we want to use the LTS version of NodeJS
// to get best performance and features
runtime: Runtime.NODEJS_18_X,
// your lambda function path
entry: "lambdas/hello-lambda.ts",
// additional config for esbuild
bundling: {
// we want to use ESM instead of CJS
format: OutputFormat.ESM
}
});
We want to use the LTS version of NodeJS to get best performance and features, e.g. fetch
, aws-sdk-v3
. So we set the runtime to NODEJS_18_X
.
Then we specify the entry
path for our Lambda function, which we are going to write in the next section.
We can pass additional esbuild config inside bundling
parameter.
For example if we want to use ESM
format instead of CommonJS
, we can simply pass OutputFormat.ESM
in the format
param.
Writing our Lambda function in TypeScript
In the previous section in entry path we put lambdas/hello-lambda.ts
, which means we need to create a directory called lambdas
in the root directory and create a file hello-lambda.ts
.
You can name the directory or path anything you like. I have kept it like this to keep the structure organized and scalable.
Writing our Lambda function in TypeScript is simple
We simply export a function called handler
and return a string
or an object
from it.
export const handler = async () => {
return "I Love AWS Lambdas";
};
Deploying our CDK code
Install esbuild locally to avoid bundling of Lambdas in docker.
npm i -D esbuild
AWS CLI Setup
Make sure you have AWS CLI setup, it’s very easy and straight forward. Check out our tutorial on setting up AWS CLI from scratch.
Bootstrapping our CDK environment
Before we deploy we need to ensure our Cloudformation
has proper roles and permission to deploy the services.
Simply run
npx cdk bootstrap
You might be asked to enter
(y/n)
, simply hity
and enter on your keyboard.
Your stack will be bootstrapped in no time and you will get to see something like this.
⏳ Bootstrapping environment aws://205979422636/us-east-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
âś… Environment aws://205979422636/us-east-1 bootstrapped (no changes).
By default it takes the role policy from your AWS CLI account which you had set up, while creating the user in IAM-Console.
You can change it by passing --cloudformation-execution-policies your-policy-arn
.
Deploying CDK stack
Now all you need to do is run the deploy command.
npx cdk deploy
You will get to see the changes in your resources before deploying your services, simply process with entering y
on your keyboard.
To skip the approval, you can simply go to your cdk.json
and add "requireApproval": "never"
.

After a while you will get to see your stack has been deployed.
âś… HelloLambdaStack
✨ Deployment time: 60.84s
Stack ARN:
arn:aws:cloudformation:us-east-1:205979422636:stack/HelloLambdaStack/3c39a160-c006-11ed-a6a5-0ee1434b988d
✨ Total time: 63.74s
Now you will be able to see your lambda function in the AWS Lambda console.

You might be thinking why the Lambda name is so big, we will work on that in the upcoming section.
Get the Lambda ARN or name in CDK output
Simply assign your lambda function into a variable, so that you can use it later.
const lambdaFn = new NodejsFunction
Use CfnOutput
to log the variable
import { CfnOutput } from "aws-cdk-lib";
new CfnOutput(this, "LambdaFn-Arn", {
value: lambdaFn.functionArn,
});
Full code with CDK output
import * as cdk from "aws-cdk-lib";
import { CfnOutput } from "aws-cdk-lib";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction, OutputFormat } from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
export class HelloLambdaStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const lambdaFn = new NodejsFunction(this, "hello-lambda", {
runtime: Runtime.NODEJS_18_X,
entry: "lambdas/hello-lambda.ts",
bundling: {
format: OutputFormat.ESM,
},
});
new CfnOutput(this, "LambdaFn-name", {
value: lambdaFn.functionName,
});
}
}
You can now run cdk deploy
command again and you will be greeted with your Lambda ARN.
âś… HelloLambdaStack
✨ Deployment time: 31.98s
Outputs:
HelloLambdaStack.LambdaFnname = HelloLambdaStack-hellolambdaDE420C60-WgN3a4k4Gn7c
Stack ARN:
arn:aws:cloudformation:us-east-1:205979422636:stack/HelloLambdaStack/3c39a160-c006-11ed-a6a5-0ee1434b988d
✨ Total time: 34.92s
Invoking Lambda function with AWS CLI
Since we have the Lambda name now we can simply use AWS CLI to invoke our Lambda.
aws lambda invoke --function-name your_lambda_fn_name response.txt
Wait to see a 200
response.
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
If you open the response.txt
, you will get to see your response:
"I Love AWS Lambdas"
Making your Lambda truly yours!
Give your lambda a name and description
It’s as easy as eating a cake, just pass the name you’d like to give your lambda in functionName
.
You can add a description as well if you’d like.
const lambdaFn = new NodejsFunction(this, "hello-lambda", {
functionName: 'my-hello-lambda',
description: 'My hello lambda using CDK',
// rest of your config ...
});
Now run cdk deploy
and you’ll see your lambda name getting updated.
✨ Deployment time: 50.66s
Outputs:
HelloLambdaStack.LambdaFnname = my-hello-lambda
Creating API using Lambda function URL
Install aws-lambda
types
We are using TypeScript and there is a reason for that, so let’s install Lambda types.
npm i -D @types/aws-lambda
Let’s add types in our Lambda function
Let’s import API gateway event and result from aws-lambda
in lambdas/hello-lambda.ts
.
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
export const handler = async (
e: APIGatewayProxyEventV2
): Promise<APIGatewayProxyResultV2> => {
console.log(e);
return {
statusCode: 200,
// let's just return the whole event
body: JSON.stringify(e),
};
};
Adding function URL in Lambda CDK Stack
Let’s import the FunctionUrlAuthType
and add function URL by calling the addFunctionUrl
method, passing auth type as none (just for demo purpose).
Warning: Passing
authType: NONE
makes your API public and anyone will the URL will able to invoke your Lambda, which will incur charges.
import { FunctionUrlAuthType, Runtime } from "aws-cdk-lib/aws-lambda";
const fnUrl = lambdaFn.addFunctionUrl({
authType: FunctionUrlAuthType.NONE,
});
new CfnOutput(this, "LambdaFn-url", {
value: fnUrl.url,
});
At the end we will simply log our URL using CfnOutput
.
Now let’s deploy our stack again with cdk deploy
.
âś… HelloLambdaStack
✨ Deployment time: 58.42s
Outputs:
HelloLambdaStack.LambdaFnname = my-hello-lambda
HelloLambdaStack.LambdaFnurl = https://cn2jqjlbyvjxoz3iovliqkmd6a0wmyke.lambda-url.us-east-1.on.aws/
As you can see above we get a sweet lambda URL with .on.aws
, pretty cool?
Let’s visit our Lambda URL and see what we get
You can see all the details of the event like headers
, method
, path
, sourceIp
and much more.
{
"version": "2.0",
"routeKey": "$default",
"rawPath": "/",
"rawQueryString": "",
"headers": {
"sec-fetch-mode": "navigate",
"x-amzn-tls-version": "TLSv1.2",
"sec-fetch-site": "none",
"accept-language": "en-US,en;q=0.5",
"x-forwarded-proto": "https",
"x-forwarded-port": "443",
"x-forwarded-for": "103.154.2.142",
"sec-fetch-user": "?1",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"sec-gpc": "1",
"x-amzn-tls-cipher-suite": "ECDHE-RSA-AES128-GCM-SHA256",
"x-amzn-trace-id": "Root=1-640c91d7-067b791a49eb16ae5e2ac494",
"host": "cn2jqjlbyvjxoz3iovliqkmd6a0wmyke.lambda-url.us-east-1.on.aws",
"upgrade-insecure-requests": "1",
"accept-encoding": "gzip, deflate, br",
"sec-fetch-dest": "document",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0"
},
"requestContext": {
"accountId": "anonymous",
"apiId": "cn2jqjlbyvjxoz3iovliqkmd6a0wmyke",
"domainName": "cn2jqjlbyvjxoz3iovliqkmd6a0wmyke.lambda-url.us-east-1.on.aws",
"domainPrefix": "cn2jqjlbyvjxoz3iovliqkmd6a0wmyke",
"http": {
"method": "GET",
"path": "/",
"protocol": "HTTP/1.1",
"sourceIp": "103.154.2.142",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0"
},
"requestId": "545fcd55-32de-4409-af1b-e1f58a15da59",
"routeKey": "$default",
"stage": "$default",
"time": "11/Mar/2023:14:36:07 +0000",
"timeEpoch": 1678545367500
},
"isBase64Encoded": false
}
From here you can take your creativity to the next level and do whatever you want in your Lambda.
Here are the list of things you can do with AWS Lambda:
- Create contact forms with DynamoDB and Lambda
- Server side render (SSR) your HTML pages for SEO
- Create image gallery using S3 and Lambda
- Build Telegram chat bots with Telegram API
- Generate YouTube download links using
ytdl-core
The possibilities are endless, you just have to think about it.
Finish it off by Destroying CDK Stack
If you are done with your big or little experiment and you wanna destroy everything to be on the safe side, you can simply execute:
npx cdk destroy
You will be presented with:
Are you sure you want to delete: HelloLambdaStack (y/n)? y
HelloLambdaStack: destroying... [1/1]
âś… HelloLambdaStack: destroyed
I hope you enjoyed reading this article and I solved your problem. If not feel free to reach out to us.