DEV Community

JImmyWong for AWS Community Builders

Posted on • Originally published at Medium on

CDKTF deploy serverless application

CDKTF basic config and install can read my before story

Terraform CDK(CDKTF) create aws s3 bucket

this time we need to deploy serverless apps

import {Construct} from "constructs";
import {App, TerraformStack} from "cdktf";
import {S3Bucket} from "@cdktf/provider-aws/lib/s3-bucket";
import {DbInstance} from "@cdktf/provider-aws/lib/db-instance";
import {AwsProvider} from "@cdktf/provider-aws/lib/provider";
import {S3BucketWebsiteConfiguration} from "@cdktf/provider-aws/lib/s3-bucket-website-configuration";
import {IamRole} from "@cdktf/provider-aws/lib/iam-role";
import {S3BucketObject} from "@cdktf/provider-aws/lib/s3-bucket-object";
import {S3BucketPolicy} from "@cdktf/provider-aws/lib/s3-bucket-policy";
import {S3BucketPublicAccessBlock} from "@cdktf/provider-aws/lib/s3-bucket-public-access-block";
import {ApiGatewayRestApi} from "@cdktf/provider-aws/lib/api-gateway-rest-api";
import {LambdaFunction} from "@cdktf/provider-aws/lib/lambda-function";
import {ApiGatewayResource} from "@cdktf/provider-aws/lib/api-gateway-resource";
// import {ApiGatewayMethod} from "@cdktf/provider-aws/lib/api-gateway-method";
// import {ApiGatewayIntegration} from "@cdktf/provider-aws/lib/api-gateway-integration";
import {ApiGatewayStage} from "@cdktf/provider-aws/lib/api-gateway-stage";
import {ApiGatewayDeployment} from "@cdktf/provider-aws/lib/api-gateway-deployment";
// import {ApiGatewayIntegrationResponse} from "@cdktf/provider-aws/lib/api-gateway-integration-response";
// import {ApiGatewayMethodResponse} from "@cdktf/provider-aws/lib/api-gateway-method-response";
import {CloudwatchLogGroup} from "@cdktf/provider-aws/lib/cloudwatch-log-group";
import {IamPolicyAttachment} from "@cdktf/provider-aws/lib/iam-policy-attachment";
import * as path from "path";
import {LambdaLayerVersion} from "@cdktf/provider-aws/lib/lambda-layer-version";
import {SecurityGroup} from "@cdktf/provider-aws/lib/security-group";
import {VpcSecurityGroupIngressRule} from "@cdktf/provider-aws/lib/vpc-security-group-ingress-rule";
import {DbSubnetGroup} from "@cdktf/provider-aws/lib/db-subnet-group";
import {Subnet} from "@cdktf/provider-aws/lib/subnet";
import {Vpc} from "@cdktf/provider-aws/lib/vpc";
// import {NatGateway} from "@cdktf/provider-aws/lib/nat-gateway";
import {RouteTable} from "@cdktf/provider-aws/lib/route-table";
import {InternetGateway} from "@cdktf/provider-aws/lib/internet-gateway";
import {MainRouteTableAssociation} from "@cdktf/provider-aws/lib/main-route-table-association";
import {IamPolicy} from "@cdktf/provider-aws/lib/iam-policy";
import {VpcSecurityGroupEgressRule} from "@cdktf/provider-aws/lib/vpc-security-group-egress-rule";

// import {DbSubnetGroup} from "@cdktf/provider-aws/lib/db-subnet-group";

class MyStack extends TerraformStack {
    constructor(scope: Construct, id: string) {
        super(scope, id);

        new AwsProvider(this, 'aws', {
            region: 'ap-east-1', // Update the region as needed
            accessKey: '', // your accessKey
            secretKey: '' // your secretKey
        });

        const my_vpc = new Vpc(this, "MyVpc", {
            cidrBlock: '10.0.0.0/16',
            enableDnsHostnames: true
        })

        new Subnet(this, 'public_subnet1', {
            vpcId: my_vpc.id,
            cidrBlock: "10.0.101.0/24",
            availabilityZone: "ap-east-1a"
        });
        new Subnet(this, 'public_subnet2', {
            vpcId: my_vpc.id,
            cidrBlock: "10.0.102.0/24",
            availabilityZone: "ap-east-1b"
        });
        new Subnet(this, 'public_subnet3', {
            vpcId: my_vpc.id,
            cidrBlock: "10.0.103.0/24",
            availabilityZone: "ap-east-1c"
        });

        const private_subnet1 = new Subnet(this, 'private_subnet1', {
            vpcId: my_vpc.id,
            cidrBlock: "10.0.1.0/24",
            availabilityZone: "ap-east-1a"
        });
        const private_subnet2 = new Subnet(this, 'private_subnet2', {
            vpcId: my_vpc.id,
            cidrBlock: "10.0.2.0/24",
            availabilityZone: "ap-east-1b"
        });
        const private_subnet3 = new Subnet(this, 'private_subnet3', {
            vpcId: my_vpc.id,
            cidrBlock: "10.0.3.0/24",
            availabilityZone: "ap-east-1c"
        });
        // new NatGateway(this, 'NatGateway', {
        // subnetId: private_subnet1.id,
        // connectivityType: "private"
        // });
        const igw = new InternetGateway(this, 'MyInternetGateway', {
            vpcId: my_vpc.id,
        });

        // Create a route table for the public subnet and add a route to the internet gateway
        const main_route = new RouteTable(this, 'PublicRouteTable', {
            vpcId: my_vpc.id,
            route: [
                {
                    cidrBlock: '0.0.0.0/0',
                    gatewayId: igw.id,

                }
            ]
        });

        new MainRouteTableAssociation(this, 'MainRouteTableAssociation', {
            vpcId: my_vpc.id,
            routeTableId: main_route.id
        })

        // publicRouteTable.createRoute('PublicRoute', {
        // destinationCidrBlock: '0.0.0.0/0',
        // gatewayId: igw.id,
        // });

        // Create an S3 bucket
        const noodle_web = new S3Bucket(this, 'myS3Bucket', {
            bucket: '', // Replace with your preferred bucket name
            serverSideEncryptionConfiguration: {
                rule: {
                    applyServerSideEncryptionByDefault: {
                        sseAlgorithm: 'AES256',
                    },
                }
            },
        });

        new S3BucketPublicAccessBlock(this, 'S3BucketPublicAccessBlock', {
            bucket: noodle_web.id,
            blockPublicAcls: false,
            blockPublicPolicy: false,
            ignorePublicAcls: false,
            restrictPublicBuckets: false,
        })
        new S3BucketWebsiteConfiguration(this, 'noodle', {
            bucket: noodle_web.bucket,
            indexDocument: {
                suffix: 'index.html'
            }
        });

        const bucketPolicy = JSON.stringify({
            Version: '2012-10-17',
            Statement: [
                {
                    Sid: 'AllowGetObject',
                    Effect: 'Allow',
                    Principal: '*',
                    Action: ['s3:GetObject'],
                    Resource: [`${noodle_web.arn}/*`],
                },
            ],
        });

        new S3BucketPolicy(this, 'MyBucketPolicy', {
            bucket: noodle_web.bucket,
            policy: bucketPolicy,
        });
        const ingress_psql_sg = new SecurityGroup(this, 'rdsSecurityGroup', {
            vpcId: my_vpc.id,
        });

        new VpcSecurityGroupIngressRule(this, 'ingress_psql_rule', {
            securityGroupId: ingress_psql_sg.id,
            cidrIpv4: "10.0.0.0/8",
            fromPort: 5432,
            ipProtocol: "tcp",
            toPort: 5432
        })

        new VpcSecurityGroupEgressRule(this, 'Egress_psql_rule', {
            securityGroupId: ingress_psql_sg.id,
            cidrIpv4: "0.0.0.0/0",
            fromPort: 5432,
            ipProtocol: "tcp",
            toPort: 5432
        })

        new S3BucketObject(this, 'website_noodle', {
            bucket: noodle_web.id,
            key: 'index.html',
            source: '/Users/jimmywong/IdeaProjects/noodle/noodles/website/index.html',
            contentType: "text/html"
        })

        const Db_Subnet_Group = new DbSubnetGroup(this, 'myDbSubnetGroup', {
            name: 'my-db-subnet-group',
            description: 'My custom DB subnet group',
            subnetIds: [private_subnet1.id, private_subnet2.id, private_subnet3.id]
        });
        // Create an RDS PostgreSQL database
        new DbInstance(this, 'myRdsInstance', {
            allocatedStorage: 20,
            engine: 'postgres',
            instanceClass: 'db.t3.micro',
            // instanceClass: 'db.m5.large',
            username: '',// your sql username
            password: '', // your sql password
            skipFinalSnapshot: true,
            vpcSecurityGroupIds: [ingress_psql_sg.id],
            dbSubnetGroupName: Db_Subnet_Group.name,
            // publiclyAccessible: true,
            identifier: "" // your db name
        });

        const lambdaRole = new IamRole(this, 'lambdaRole', {
            name: 'AWSLambdaBasicExecutionRole',
            assumeRolePolicy: JSON.stringify({
                Version: '2012-10-17',
                Statement: [
                    {
                        Action: 'sts:AssumeRole',
                        Effect: 'Allow',
                        Principal: {
                            Service: 'lambda.amazonaws.com',
                        },
                    },
                ],
            }),
        });

        const lambda_policy = new IamPolicy(this, 'lambda_policy', {
            policy: JSON.stringify({
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Action": [
                            "ec2:DescribeNetworkInterfaces",
                            "ec2:CreateNetworkInterface",
                            "ec2:DeleteNetworkInterface",
                            "ec2:DescribeInstances",
                            "ec2:AttachNetworkInterface",
                            "logs:CreateLogGroup",
                            "logs:CreateLogStream",
                            "logs:PutLogEvents"
                        ],
                        "Resource": "*"
                    }
                ]
            })
        })

        // Attach a managed policy to the IAM role (optional)
        new IamPolicyAttachment(this, 'MyPolicyAttachment', {
            name: 'inputLambdaPolicyAttachment',
            policyArn: lambda_policy.arn, // Replace with the desired policy ARN
            roles: [lambdaRole.name],
        });

        const lambda_layer = new LambdaLayerVersion(this, 'pgLayer', {
            filename: path.resolve(__dirname, '/Users/jimmywong/IdeaProjects/noodle/noodles/lambda-layer-pg/lambda-layer-pg.zip'), // Replace with the path to your ZIP archive
            layerName: 'pg_layer',
            compatibleRuntimes: ['nodejs18.x'], // Adjust the runtime version as needed
        });

        const input_file_path = '/Users/jimmywong/IdeaProjects/noodle/noodles/lambda/input_data_to_db.zip';

        const output_file_path = '/Users/jimmywong/IdeaProjects/noodle/noodles/lambda/output_data_from_db.zip'
        // Create the Lambda function
        const input_lambda = new LambdaFunction(this, 'my_input_LambdaFunction', {
            functionName: 'input_noodle_data',
            runtime: 'nodejs18.x',
            handler: 'input_data_to_db.handler',
            filename: input_file_path,
            role: lambdaRole.arn,
            vpcConfig: {
                subnetIds: [private_subnet1.id, private_subnet2.id, private_subnet3.id],
                securityGroupIds: [ingress_psql_sg.id]
            },
            // filename: '/Users/jimmywong/IdeaProjects/noodle/noodles/lambda/input_data_to_db.zip',
            layers: [lambda_layer.arn],
        });

        const output_lambda = new LambdaFunction(this, 'my_output_LambdaFunction', {
            functionName: 'query_noodle_data',
            runtime: 'nodejs18.x',
            handler: 'output_data_from_db.handler',
            role: lambdaRole.arn,
            filename: output_file_path,
            layers: [lambda_layer.arn],
            vpcConfig: {
                subnetIds: [private_subnet1.id, private_subnet2.id, private_subnet3.id],
                securityGroupIds: [ingress_psql_sg.id]
            }
        });

        const restApi = new ApiGatewayRestApi(this, 'MyRestApi', {
            name: 'my-rest-api', // Replace with your desired REST API name
            description: 'My REST API description', // Replace with your desired REST API description
        });

        new ApiGatewayResource(this, 'MyResource', { //aws_api_gateway_resource
            restApiId: restApi.id,
            parentId: restApi.rootResourceId,
            pathPart: 'submit', // Replace with your desired resource path
        });

        // Create a POST method for the resource
        // const input_method = new ApiGatewayMethod(this, 'MyPostMethod', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // httpMethod: 'POST',
        // authorization: 'NONE', // No authorization required for this example, update as needed
        // });

        // Create a GET method for the resource
        // const output_method = new ApiGatewayMethod(this, 'MyGetMethod', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // httpMethod: 'GET',
        // authorization: 'NONE', // No authorization required for this example, update as needed
        // });

        // Create the integration response for CORS
        // const ApiGatewayMethodResponse_input = new ApiGatewayMethodResponse(this, 'ApiGatewayMethodResponseinput', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // httpMethod: input_method.httpMethod,
        // statusCode: "200",
        // responseModels: {
        // "application/json": "Empty"
        // },
        // responseParameters: {
        // "method.response.header.Access-Control-Allow-Headers": true,
        // "method.response.header.Access-Control-Allow-Methods": true,
        // "method.response.header.Access-Control-Allow-Origin": true
        // },
        // });

        // new ApiGatewayIntegrationResponse(this, 'MyIntegrationResponse_input', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // httpMethod: input_method.httpMethod,
        // statusCode: ApiGatewayMethodResponse_input.statusCode,
        // responseParameters: {
        // "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
        // "method.response.header.Access-Control-Allow-Methods": "'POST'",
        // "method.response.header.Access-Control-Allow-Origin": "'*'"
        // },
        // });

        // new ApiGatewayIntegration(this, 'PostApiGatewayIntegration', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // integrationHttpMethod: "POST",
        // type: "AWS",
        // uri: input_lambda.invokeArn,
        // httpMethod: input_method.httpMethod
        // });

        const ApiGatewayDeployment_input = new ApiGatewayDeployment(this, 'ApiGatewayDeployment', {
            restApiId: restApi.id,
        });

        new ApiGatewayStage(this, 'ApiGatewayStage1', {
            stageName: "", // your stage name
            restApiId: restApi.id,
            deploymentId: ApiGatewayDeployment_input.id
        });
        //
        // new ApiGatewayIntegration(this, 'GetApiGatewayIntegration', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // integrationHttpMethod: "GET",
        // type: "AWS",
        // uri: output_lambda.invokeArn,
        // httpMethod: output_method.httpMethod,
        // });
        // new ApiGatewayIntegrationResponse(this, 'MyIntegrationResponse_output', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // httpMethod: "GET",
        // statusCode: "200",
        // // responseParameters: {
        // // "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
        // // "method.response.header.Access-Control-Allow-Methods": "'GET'",
        // // "method.response.header.Access-Control-Allow-Origin": "'*'"
        // // },
        // });
        // new ApiGatewayMethodResponse(this, 'ApiGatewayMethodResponseoutput', {
        // restApiId: restApi.id,
        // resourceId: resource.id,
        // httpMethod: output_method.httpMethod,
        // statusCode: "200",
        // responseModels: {
        // "application/json": "Empty"
        // },
        // // responseParameters: {
        // // "method.response.header.Access-Control-Allow-Headers": true,
        // // "method.response.header.Access-Control-Allow-Methods": true,
        // // "method.response.header.Access-Control-Allow-Origin": true
        // // },
        // });

        const input_cloudwatch_path = '/aws/lambda/' + input_lambda.functionName

        new CloudwatchLogGroup(this, 'lambda_input_log_group', {
            name: input_cloudwatch_path,
            retentionInDays: 7,
        });
        const output_cloudwatch_path = '/aws/lambda/' + output_lambda.functionName

        new CloudwatchLogGroup(this, 'lambda_output_log_group', {
            name: output_cloudwatch_path,
            retentionInDays: 7,
        });
    }
}

const app = new App();
new MyStack(app, "noodles");
app.synth();
Enter fullscreen mode Exit fullscreen mode

this code will create new s3 with the object, also, it will create api-gateway and lambda with the in and out like the diagram


WorkFlow

the file structure like


file structure

so, we can develop the apps use nodejs with the typescript in the cdktf, about the lib part i used pg, if you need the details, you can read the REF Doc

in this project, you need ready lambda code , website html code with css and javascript, i don’t know why the resource and deploy have issues in the apigateway with cdktf, i will keep to improve this part, my code should work in create api gateway , but may need the user custom deploy in the console


Deploy the API in api gateway


Deploy the API


click in stages


you can get the api in your new Stages

REF:

NodeJS Runtime Environment with AWS Lambda Layers

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Create a simple OTP system with AWS Serverless cover image

Create a simple OTP system with AWS Serverless

Implement a One Time Password (OTP) system with AWS Serverless services including Lambda, API Gateway, DynamoDB, Simple Email Service (SES), and Amplify Web Hosting using VueJS for the frontend.

Read full post