Building a Sample Lambda Function and API Gateway with CDK for Terraform
Tags: cdk, terraform, aws, typescript
Published: 2022-02-04 15:35:37

In this post, you learn how to use CDK for Terraform to build a sample serverless application on AWS. We are going to use AWS lambda and API Gateway to build this application. 


First thing's first, let's initialize a CDKTF project. Similar to Cloudformation CDK, you can run the following command to initialize the project:

    cdktf init

Then it will ask you for the language of your choice and name and description for your project. I opted for Typescript but you can use whatever language you are comfortable with.
The "cdktf init" command will create the project structure for us. As you might know, Terraform has a very large provider ecosystem. To continue with the project, you need to specify which provider you want to use. To do that, open the cdktf.json and change the "terraformProviders" section to:
    
      "terraformProviders": [
        "aws@>3.0"
      ]

Of course, you can use the version of your choice. After that, you need to run the following command to pull down the providers:

    cdktf get    

Now we are ready to begin. In the main.ts file, add the following imports:

    import { Construct } from "constructs";
    import { App, AssetType, TerraformAsset, TerraformOutput, TerraformStack } from "cdktf";
    import {lambdafunction, s3, apigatewayv2, iam, AwsProvider} from "./.gen/providers/aws"
    import path = require("path");

It is worth mentioning, that unlike Cloudformation CDK, you will get a compile error if you have unused imports or variables in your code.
Now add the following code to specify the provider:

    new AwsProvider(this, "aws", {
      region: "eu-west-1"
    });
    
You also need to specify where your lambda function is. I created a folder called "src" and used TerraformAsset to import it as follows:

    const asset = new TerraformAsset(this, "asset", {
      path: path.resolve(__dirname,'./src'),
      type:AssetType.ARCHIVE
    });

Now we need to create the S3 Bucket and Object to upload our TerraformAsset function:

    const assetBucket = new s3.S3Bucket(this, "assetBucket", {
      bucket:"a-unique-bucket-name"
    });

    const lambdaArchive = new s3.S3BucketObject(this, "lambdaArchive", {
      bucket:assetBucket.bucket,
      key:asset.fileName,
      source:asset.path
    });

Then create the IAM Policy and Role for the lambda function:

  const lambdaRole = {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "lambda.amazonaws.com"
          },
          "Action": "sts:AssumeRole",
          "Sid": "",
        }
      ]
    };

    const role = new iam.IamRole(this, "role", {
      assumeRolePolicy: JSON.stringify(lambdaRole),
      name: "my-lambda-role"
    });

    new iam.IamRolePolicyAttachment(this, "rolePolicy", {
      policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
      role: role.name
    });

After that, we can specify the configuration of our lambda function:

    const lambdaFunc = new lambdafunction.LambdaFunction(this, "lambdaFunc", {
      functionName: "my-lambda-function",
      runtime: "nodejs14.x",
      handler: "index.handler",
      role: role.arn,
      s3Bucket: assetBucket.bucket,
      s3Key: lambdaArchive.key
    });

Then, we create an API Gateway to receive HTTP Requests from the Internet:

    const api = new apigatewayv2.Apigatewayv2Api(this, "api", {
      name: name, 
      protocolType: "HTTP",
      target: lambdaFunc.arn
    });

Finally, you need to create a LambdaPermission Policy to allow the API Gateway to invoke our Lambda Function:

    new lambdafunction.LambdaPermission(this, "apig-lambda", {
      functionName: lambdaFunc.functionName,
      action: "lambda:InvokeFunction",
      principal: "apigateway.amazonaws.com",
      sourceArn: `${api.executionArn}/*/*`,
    });

If you like to see the endpoint of your API and test it curl or postman, you can use the following command:

    new TerraformOutput(this, "apiUrl", {
      value: api.apiEndpoint
    });

Don't forget to add your lambda function in the "src" folder; create a file called index.ts and add the functionality that you want. I only wanted to send a hello world response:

    export const handler = async () => {
        return { statusCode: 200, body: 'hello world'  };
    }

Regarding deploying this code, you will find your way if you have used CDK Cloudformation or Terraform HCL.
You can run the following commands to deploy the project:
    
    npm run build
    cdktf plan OR cdktf synth
    cdktf apply OR cdktf deploy

The youtube video: https://youtu.be/3rVgMYc7jkg
Github: https://github.com/pedramha/cdktf-aws-example