Skip to main content
  1. Projects/

AWS CDK with TypeScript

Aws Cdk Typescript
Falk Zeh
Author
Falk Zeh
Data Engineer & Humanoid Robotics Student
Table of Contents

Featured

AWS CDK with TypeScript - API Gateway, Lambda and S3
#

So far I have been using terraform or just the AWS console to create resources in AWS. However, I have been hearing a lot about the AWS Cloud Development Kit (CDK) and I wanted to give it a try. CDK is an open-source software development framework to define cloud infrastructure in code and provision it through AWS CloudFormation. It allows developers to define infrastructure using familiar programming languages such as TypeScript, Python, Java, and C#.

For this demo, I’d like to create a basic CDK application using TypeScript. The application will create an API Gateway, a Lambda function and a S3 bucket. The Lambda function will be triggered by the API Gateway and will store the request body in the S3 bucket.

Setup
#

Since I will be using TypeScript, I need to install Node.js and npm first:

sudo apt update
sudo apt install nodejs

Check if the installation was successful:

$ npm -v
8.19.4

Now I can install TypeScript 3.8 or later

npm -g install typescript

Since I’m starting from scratch, I will also need to install and authenticate the AWS CLI:

sudo apt install awscli
aws configure
$ aws configure
AWS Access Key ID [None]: AKIATCKAT5G2JMIQ2FMV
AWS Secret Access Key [None]: xxx
Default region name [None]: eu-central-1
Default output format [None]: json

Note that I already created an IAM user with the necessary permissions and obtained the access key and secret key via the AWS console.

Last step is to install the AWS CDK toolkit:

npm install -g aws-cdk
cdk --version
2.127.0 (build 6c90efc)

Create a new CDK Application
#

I will start by creating a new CDK application using the following command:

mkdir app
cd app
cdk init app --language typescript

This will create a bunch of files and folders in the app directory. In the lib folder one can find the app-stack.ts file. This is where I will define the resources I want to create.

Define the CDK Stack
#

Now I’ll be defining the resources I want to create in the app-stack.ts file. I will create an API Gateway, a Lambda function and a S3 bucket. The Lambda function will be triggered by the API Gateway and will store the request body in the S3 bucket.

Therefore I will need to install and then import the following modules:

npm install @aws-cdk/aws-apigateway
npm install @aws-cdk/aws-lambda
npm install @aws-cdk/aws-s3

lib/app-stack.ts:

import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';

S3 Bucket
#

First comes the S3 bucket:

const bucket = new s3.Bucket(this, 'PayloadBucket', {
    removalPolicy: cdk.RemovalPolicy.DESTROY,
});

This will create a S3 bucket named PayloadBucket and set the removal policy to DESTROY. This means that when the stack is deleted, the bucket will also be deleted.

Lambda Function
#

Next, I will create the Lambda function:

const dumpPayloadFunction = new lambda.Function(this, 'DumpPayloadFunction', {
    runtime: lambda.Runtime.NODEJS_16_X,
    handler: 'index.handler',
    code: lambda.Code.fromAsset('lambda'),
    environment: {
        BUCKET_NAME: bucket.bucketName,
    },
});

This will create a Lambda function named DumpPayloadFunction using the Node.js 16 runtime. The function code is located in the lambda folder and the handler is index.handler. The function will also have access to the BUCKET_NAME environment variable.

For the Lambda function itself, I will create a new folder called lambda and add a file called index.js with the following content:

const AWS = require('aws-sdk');
const s3 = new AWS.S3();

exports.handler = async (event) => {
    const params = {
        Bucket: process.env.BUCKET_NAME,
        Key: `payload-${Date.now()}.json`,
        Body: JSON.stringify(event),
    };
    await s3.putObject(params).promise();
    return {
        statusCode: 200,
        body: JSON.stringify('Payload stored in S3'),
    };
};

The function is pretty basic. It will store the request body in a new JSON file in the S3 bucket and return a 200 status code with the message Payload stored in S3.

API Gateway
#

Finally, the API Gateway:

const api = new apigateway.RestApi(this, 'PayloadApi', {
    restApiName: 'Payload Service',
    description: 'This service stores the payload in S3',
    defaultMethodOptions: {
        authorizationType: apigateway.AuthorizationType.NONE,
    },
});

This will create a new API Gateway named PayloadApi with the name Payload Service and the description This service stores the payload in S3. The default method options will be set to NONE meaning that the API will be publicly accessible as this is just a demo.

I will also create a new resource and a new method for the API Gateway:

const payloadResource = api.root.addResource('payload');
const postPayloadIntegration = new apigateway.LambdaIntegration(dumpPayloadFunction);
payloadResource.addMethod('POST', postPayloadIntegration);

Creating the new resource and method will allow me to send a POST request to the /payload endpoint which will trigger the Lambda function.

Bring it all together and deploy
#

Finally, I will add the following lines to the app-stack.ts file to bring everything together:

export class AppStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);

        const bucket = new s3.Bucket(this, 'PayloadBucket', {
            removalPolicy: cdk.RemovalPolicy.DESTROY,
        });

        const dumpPayloadFunction = new lambda.Function(this, 'DumpPayloadFunction', {
            runtime: lambda.Runtime.NODEJS_16_X,
            handler: 'index.handler',
            code: lambda.Code.fromAsset('lambda'),
            environment: {
                BUCKET_NAME: bucket.bucketName,
            },
        });

        const api = new apigateway.RestApi(this, 'PayloadApi', {
            restApiName: 'Payload Service',
            description: 'This service stores the payload in S3',
            defaultMethodOptions: {
                authorizationType: apigateway.AuthorizationType.NONE,
            },
        });

        const payloadResource = api.root.addResource('payload');
        const postPayloadIntegration = new apigateway.LambdaIntegration(dumpPayloadFunction);
        payloadResource.addMethod('POST', postPayloadIntegration);
    }
}

And now I can deploy the stack using the following command:

cdk deploy
✨  Synthesis time: 27.69s

AppStack:  start: Building f82ef9ee2ffefa0afc137c8ee83aaf1c0cb7e6a968e58dad3a4713360397bbd6:current_account-current_region
AppStack:  success: Built f82ef9ee2ffefa0afc137c8ee83aaf1c0cb7e6a968e58dad3a4713360397bbd6:current_account-current_region
AppStack:  start: Building 97b88f12a74a83cd057e74da4360a428d1b54a86c8bb8cd52e065f2057cdb55d:current_account-current_region
AppStack:  success: Built 97b88f12a74a83cd057e74da4360a428d1b54a86c8bb8cd52e065f2057cdb55d:current_account-current_region
AppStack:  start: Publishing f82ef9ee2ffefa0afc137c8ee83aaf1c0cb7e6a968e58dad3a4713360397bbd6:current_account-current_region
AppStack:  start: Publishing 97b88f12a74a83cd057e74da4360a428d1b54a86c8bb8cd52e065f2057cdb55d:current_account-current_region
AppStack:  success: Published 97b88f12a74a83cd057e74da4360a428d1b54a86c8bb8cd52e065f2057cdb55d:current_account-current_region
AppStack:  success: Published f82ef9ee2ffefa0afc137c8ee83aaf1c0cb7e6a968e58dad3a4713360397bbd6:current_account-current_region
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

...

Do you wish to deploy these changes (y/n)? y
AppStack: deploying... [1/1]
AppStack: creating CloudFormation changeset...

 ✅  AppStack

✨  Deployment time: 57.99s

Outputs:
AppStack.PayloadApiEndpointD0728DC2 = https://0wg99bkbb6.execute-api.eu-central-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:eu-central-1:211125791156:stack/AppStack/16e0c9f0-cb65-11ee-b637-0ab1607eed7d

✨  Total time: 85.68s

Time for testing
#

Moment of truth! After the deployment is complete, I can test the API Gateway by sending a POST request to the /payload endpoint.

I will use curl for this:

curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello from Falk!"}' https://0wg99bkbb6.execute-api.eu-central-1.amazonaws.com/prod/payload

Aaaand {"message": "Internal server error"}. Not what I was hoping for. Let’s check the logs of the Lambda function to see what went wrong:

Error

It looks like the Lambda function does not have the necessary permissions to write to the S3 bucket. This can be fixed by adding the AWSLambdaBasicExecutionRole to the Lambda function. I will add the following line to the app-stack.ts file:

import * as iam from 'aws-cdk-lib/aws-iam';

dumpPayloadFunction.addToRolePolicy(new iam.PolicyStatement({
    actions: ['s3:PutObject'],
    resources: [bucket.bucketArn + '/*'],
}));

After deploying the stack again, the POST request to the API Gateway endpoint should now work as expected:

$ curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello from Falk!"}' https://0wg99bkbb6.execute-api.eu-central-1.amazonaws.com/prod/payload

"Payload stored in S3"

I can also verify that the payload was stored in the S3 bucket:

S3

Conclusion
#

I like the AWS CDK! It is a great way to define cloud infrastructure and somehow it feels more natural and intuitive than using terraform.

Related

ETL Pipeline Implementation
Data Etl Airflow Mysql Aws
Portal E-Ink Calendar
Esp32 Electronics 3d Printing
2DS XL Repair and Modding [Failed Project]
Modding Failed