Hills 🏔 and Skills, What's Common?

They both need you to be on top.

Cohort-1 just ended. You will get:

All yours, just at:

$99

How to Create AWS Lambda Function using TypeScript in AWS CDK

Updated on
Your first Lambda function banner

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.

cdk and lambda initialization

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 hit y 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".

hello lambda cdk bootstrap confirm img

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.

hello lambda in 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:

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.