DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Data Migration With AWS DMS and Terraform IaC
  • End-to-End Event Streaming With Kafka, Spring Boot and AWS SQS/SNS (Production-Ready Code Guide)
  • AWS Bedrock: The Future of Enterprise AI
  • Understanding Custom Authorization Mechanisms in Amazon API Gateway and AWS AppSync

Trending

  • No More Cheap Claude: 4 First Principles of Token Economics in 2026
  • Contract-First Integration: Building Scalable Systems With Flyway, OpenAPI, and Kafka
  • Stop Running Two Data Systems for One Agent Query
  • Run Gemma 4 on Your Laptop: A Hands-On Guide to Google's Latest Open Multimodal LLM
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. Using CDKTF To Create an AWS Lambda Function

Using CDKTF To Create an AWS Lambda Function

Writing Infrastructure as Code (IaC). This post shows an example of creating a simple HTML form and serving it up in a Lambda function that can be accessed from a URL.

By 
Dale Waterworth user avatar
Dale Waterworth
·
May. 10, 23 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
6.0K Views

Join the DZone community and get the full member experience.

Join For Free

Having not done much infrastructure before, writing Terraform seemed a pretty daunting task. Learning HCL and its nuances in a declarative manner and configuring it all for different environments is a bit of a learning curve. Creating the same in code using an imperative style seems a better path for a developer.

Setting Up

This is a simple example of using Terraforms cloud development kit (CDKTF) to create a Lambda function in AWS in Typescript.

To get started, follow their installation setup here.

Create a new project:

mkdir cdktf-lambda

cd cdktf-lambda

cdktf init --template="typescript" --providers="aws@~>4.0"

Follow the cmd prompts, and lastly:

npm i @cdktf/[email protected]

At the time, the dependencies were:

JSON
 
  "dependencies": {
    "@cdktf/provider-archive": "^5.0.1",
    "@cdktf/provider-aws": "13.0.0",
    "cdktf": "^0.15.5",
    "constructs": "^10.1.310"
  },
  "devDependencies": {
    "@types/jest": "^29.5.1",
    "@types/node": "^20.1.0",
    "jest": "^29.5.0",
    "ts-jest": "^29.1.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.0.4"
  }


The directory structure will have been created like so:

directory structure

The init command provides some boilerplate code to get up and running with.

The main.ts is the central file from which the code will run, and this is known as a TerraformStack. In this stack is where all the IaC will be placed.

Let's Go

CDKTF has the concept of providers, which are Terraform wrappers for third-party APIs such as AWS. We need to add one for AWS and one to handle the lambda archive bindings:

TypeScript
 
class MyStack extends TerraformStack {
  private prefix = 'dale-test-';
  private region = '<your-aws-region>'
  private accountId = '<your-aws-account>';
  
  constructor(scope: Construct, id: string) {
    super(scope, id);

    new ArchiveProvider(this, "archiveProvider");
    new AwsProvider(this, this.prefix + "aws", {
      region: this.region,
      allowedAccountIds: [this.accountId],
      defaultTags: [
        {
          tags: {
            name: this.prefix +'lambda-stack',
            version: "1.0",
          }
        }
      ]
    });

  }
}


There should be enough here to do a sanity check run:

cdktf diff

At this stage, it error'd and the tsconfig file requires the following to be added:
"ignoreDeprecations": "5.0"

A successful run should show:

cdktf-lambda  No changes. Your infrastructure matches the configuration. 

Roles

Next, add an IAM role and a policy for the Lambda:

TypeScript
 
const role = new IamRole(this, this.prefix + "iam_for_lambda", {
            assumeRolePolicy: new DataAwsIamPolicyDocument(this, this.prefix + "assume_role", {
                statement: [
                    {
                        actions: [
                            "sts:AssumeRole"
                        ],
                        effect: "Allow",
                        principals: [
                            {
                                identifiers: ["lambda.amazonaws.com"],
                                type: "Service",
                            },
                        ],
                    }
                ],
            }).json,
            name: this.prefix + "iam_for_lambda",
        });

        new IamRolePolicy(this, this.prefix + "iamPolicy", {
            name: this.prefix + `iamPolicy-state`,
            role: role.id,
            policy: new DataAwsIamPolicyDocument(this, this.prefix + "iamPolicyDoc", {
                version: "2012-10-17",
                statement: [
                    {
                        effect: "Allow",
                        actions: ["logs:CreateLogGroup"],
                        resources: [`arn:aws:logs:${this.region}:${this.accountId}:*`]
                    },
                    {
                        effect: "Allow",
                        actions: [
                            "logs:CreateLogStream",
                            "logs:PutLogEvents"
                        ],
                        resources: [
                            `arn:aws:logs:${this.region}:${this.accountId}:log-group:/aws/lambda/dale-test-manual:*`
                        ]
                    }
                ]
            }).json
        });


Lambda

We'll create a simple form and place index.html and index.js into the dist folder:

HTML
 
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta data-fr-http-equiv="x-ua-compatible" content="ie=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title>Hello from AWS Lambda!</title>
    <style type="text/css">
        @font-face {
            font-family: "sans-serif";
        }

        body {
            margin: 0;
            font-family: "Amazon Ember", Helvetica, Arial, sans-serif;
        }

        h1 {
            background-color: #232f3e;
            color: white;
            font-size: 3rem;
            font-weight: 300;
            margin: 0;
            padding: 1rem;
            text-align: center;
        }


        input[type="text"] {
            font-family: "Amazon Ember", Helvetica, Arial, sans-serif;
            flex-grow: 1;
            border: 1px solid #aab7b8;
            border-radius: 2px;
            color: #16191f;
        }

        input[type="text"]:focus {
            border: 1px solid #00a1c9;
            box-shadow: 0 0 0 1px #00a1c9;
            outline: 2px dotted transparent;
        }

        form {
            display: flex;
            flex-direction: row;
            gap: 1rem;
            padding-right: 3rem;
        }

        input[type="submit"] {
            background-color: white;
            border: 1px solid #545b64;
            border-radius: 2px;
            color: #545b64;
            cursor: pointer;
            font-weight: 700;
            padding: .4rem 2rem;
        }

        input[type="submit"]:hover {
            background-color: #f2f3f3;
            border: 1px solid #16191f;
            color: #16191f;
        }

    </style>
</head>
<body>
<h1>CDKTF Lambda Demo</h1>

<form action="/" method="GET">
    <input name="name"  placeholder="name" label="name">
    <input name="location" placeholder="location" label="location">
    <input type="submit" value="submit">
</form>
{formResults}
{debug}

</body>
</html>


JavaScript
 
const fs = require('fs');
let html = fs.readFileSync('index.html', {encoding: 'utf8'});

/**
 * Returns an HTML page containing an interactive Web-based
 * tutorial. Visit the function URL to see it and learn how
 * to build with lambda.
 */
exports.handler = async (event) => {

  let modifiedHTML = dynamicForm(html, event.queryStringParameters);
  modifiedHTML = debug(modifiedHTML, event);

  const response = {
    statusCode: 200,
    headers: {
      'Content-Type': 'text/html',
    },
    body: modifiedHTML,
  };
  return response;
};

function debug(modifiedHTML, event) {
  return modifiedHTML.replace('{debug}',
      JSON.stringify(event));
}

function dynamicForm(html, queryStringParameters) {
  let formres = '';
  if (queryStringParameters) {
    Object.values(queryStringParameters).forEach(val => {
      formres = formres + val + ' ';
    });
  }
  return html.replace('{formResults}',
      '<h4>Form Submission: ' + formres + '</h4>');
}


Now, set up the lambda:

The files are archived from the dist folder, packaged up, and set in the LambdaFunction.

A LambdaFunctionUrl is set up so it can be publicly accessed. It is also debugged out to see more details.

TypeScript
 
 const archiveFile = new DataArchiveFile(this, this.prefix +"lambda", {
   outputPath: "lambda_function_payload.zip",
   sourceDir: path.resolve(__dirname, "dist"),
   type: "zip",
 });

const lambda = new LambdaFunction(this, this.prefix +"test_lambda", {
  environment: {
    variables: {
      foo: "bar",
    },
  },
  filename: "lambda_function_payload.zip",
  functionName: "dale_test_auto",
  handler: "index.handler",
  role: role.arn,
  runtime: "nodejs16.x",
  sourceCodeHash: archiveFile.outputBase64Sha256,
});

const url = new LambdaFunctionUrl(this, this.prefix +'lambda-url', {
  functionName: lambda.functionName,
  authorizationType: 'NONE'
});

const debugOutput = new TerraformOutput(this, "lambda-function", {
  value: url,
});

console.log(debugOutput);


Thats it!

Deploying

Now when running cdktf diff we should see it will add four items:

Plan: 4 to add, 0 to change, 0 to destroy. 

  • # aws_iam_role.dale-test-iam_for_lambda (dale-test-iam_for_lambda) will be created
  • # aws_iam_role_policy.dale-test-iamPolicy (dale-test-iamPolicy) will be created
  • # aws_lambda_function.dale-test-test_lambda (dale-test-test_lambda) will be created
  • # aws_lambda_function.dale-test-test_lambda (dale-test-test_lambda) will be created

Now, deploy it.

cdktf deploy  

Plain Text
 
        Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
        
        Outputs:

        
lambda  lambda-function = {
          "authorization_type" = "NONE"
          "cors" = tolist([])
          "function_arn" = "arn:aws:lambda:eu-west-2:<id>:function:dale_test_auto"
          "function_name" = "dale_test_auto"
          "function_url" = "https://<random-url>.lambda-url.eu-west-2.on.aws/"
          "id" = "dale_test_auto"
          "invoke_mode" = "BUFFERED"
          "qualifier" = ""
          "timeouts" = null /* object */
          "url_id" = "<random>"
        }


The function_url now is the URL to view the lambda as shown below:

CDKTF Lambda Demo

To teardown the Lambda, simply run:

Plain Text
 
cdktf destroy


Destroy complete! Resources: 4 destroyed.


The full code can be found here.

Conclusion

The code is relatively simple to create infrastructure using CDKTF. It enables a logical structuring of reusable code. Once the infrastructure grows, managing a maintaining the codebase will require less effort. 

Coming from a developer standpoint, it makes sense to create the using IaC. While being a Java developer, TS was selected due to the fact the TF also writes the core library in TS although it does support other languages too but transpired ultimately back to TS.

Not covered here, but the code can be unit tested to ensure everything is wired up correctly, although this doesn't necessarily prevent error downstream when planning and applying.

Also not covered here are advanced techniques required when passing resources between stacks and references from newly created resources at run time. All possible using CDKTF, however. That may be the topic for the next post.

I hope you enjoyed the post, and thanks for reading.

AWS AWS Lambda Terraform (software)

Opinions expressed by DZone contributors are their own.

Related

  • Data Migration With AWS DMS and Terraform IaC
  • End-to-End Event Streaming With Kafka, Spring Boot and AWS SQS/SNS (Production-Ready Code Guide)
  • AWS Bedrock: The Future of Enterprise AI
  • Understanding Custom Authorization Mechanisms in Amazon API Gateway and AWS AppSync

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook