Build TypeScript Managed App Runner with CDK
k.goto
Posted on June 24, 2023
Overview
In my last article, I wrote about building an AWS App Runner with the Go language as the Managed Runtime of choice with the AWS CDK for Go.
(By the way, Managed Runtime is a convenient way to trigger (or manually deploy) a push to GitHub without the need for a Dockerfile or ECR.)
So this time, since I'm so excited, I also tried to create an AWS App Runner with TypeScript as the managed runtime of choice, using the AWS CDK for TypeScript.
Also, as of June 2023, the L2 Construct of App Runner for CDK is currently in alpha version, but I tried building it both ways "L1 Construct" and "alpha version of L2 Construct".
Assumptions
This time, the structure, writing style, and resource definitions of the CDK stack are almost the same as those in Go (as much as possible, from the constructor to the parameters).
For this reason, this article will not go into much detail, but for a more detailed explanation, please refer to the above article on the Go version.
It is also interesting to compare how CDK is written differently between TypeScript and Go, so if you are interested, please take a look.
GitHub
All code is available on GitHub.
General Description
I do the following
- Create AutoScalingConfiguration (scaling configuration) with Custom Resource Lambda.
- Creating AutoScalingConfiguration with Lambda instead of
AwsCustomResource
in CDK module, so not only creating AutoScalingConfiguration but also updating and deleting.
- Creating AutoScalingConfiguration with Lambda instead of
- App Runner instance role creation
- AppRunner Service creation
- Create both L1 and L2(alpha).
- Auto Deploy: On
- VPC Connector creation
- Create both L1 and L2(alpha).
- Get GitHub connection ARN with AWS SDK
- Prerequisite for pre-creation (error termination if not created)
- Shell script to create GitHub connection created and included
What's different (from what I made in the Go version)
Define a custom resource Lambda
This uses the TypeScript (Node.js) familiar NodejsFunction of aws-lambda-nodejs.
Therefore, the Lambda package.json is shared with the CDK one to make it a single package. (The package.json for the application to be published in App Runner is created separately.)
const customResourceLambda = new NodejsFunction(this, "custom", {
runtime: Runtime.NODEJS_16_X,
bundling: {
forceDockerBundling: false,
},
timeout: Duration.seconds(900),
initialPolicy: [
new PolicyStatement({
actions: [
"apprunner:*AutoScalingConfiguration*",
"apprunner:UpdateService",
"apprunner:ListOperations",
],
resources: ["*"],
}),
new PolicyStatement({
actions: ["cloudformation:DescribeStacks"],
resources: [this.stackId],
}),
],
});
Provider for custom resource Lambda
In Go, I created a custom resource by passing a Lambda Arn as ServiceToken
to the CustomResource
constructor, but in TypeScript, I created a Provider
(of CustomResource) and passed its property serviceToken
to the CustomResource
constructor. TypeScript creates a Provider
(of CustomResource) and passes the serviceToken
of that property.
- CDK code
const autoScalingConfigurationProvider = new Provider(
this,
"AutoScalingConfigurationProvider",
{
onEventHandler: customResourceLambda,
},
);
// ...
const autoScalingConfiguration = new CustomResource(this, "AutoScalingConfiguration", {
resourceType: "Custom::AutoScalingConfiguration",
properties: autoScalingConfigurationProperties,
serviceToken: autoScalingConfigurationProvider.serviceToken,
});
- Lambda code
export const handler: CdkCustomResourceHandler = async function (event) {
// ...
How to handle GitHub connections
In the Go version,
- When deploying CDK, check if there is a GitHub connection, if not, create it, if there is, use it.
- If it is created or "PENDING_HANDSHAKE", it waits for a prompt and gives you time to go to the console to press a button, and then press Enter to restart the CDK process.
I had been doing something a bit more elaborate, but this time I thought it was unnecessary to go that far, so I decided to simply make a pre-configuration that says "if it's there, get it, if not, error".
I did not include the GitHub connection in the application stack as a custom resource, because I thought it might be used in the same unit as the application or in a larger unit such as an AWS account.
I also created a shell script to create the GitHub connection, so I ran it and then hit CDK.
#!/bin/bash
set -eu
cd `dirname $0`
PROFILE=""
PROFILE_OPTION=""
REGION="ap-northeast-1"
CONNECTION_NAME=""
PROVIDER_TYPE="GITHUB"
while getopts p:c: OPT; do
case $OPT in
p)
PROFILE="$OPTARG"
;;
c)
CONNECTION_NAME="$OPTARG"
;;
esac
done
if [ -z "${CONNECTION_NAME}" ]; then
echo "CONNECTION_NAME (-c) is required"
exit 1
fi
if [ -n "${PROFILE}" ]; then
PROFILE_OPTION="--profile ${PROFILE}"
fi
aws apprunner create-connection \
--connection-name ${CONNECTION_NAME} \
--provider-type ${PROVIDER_TYPE} \
${PROFILE_OPTION}
Build
When using App Runner with Managed Runtime, you must specify the build and start commands.
Build command
This time, since it is TypeScript, I used esbuild to build (transpile).
cd app && yarn install --non-interactive --frozen-lockfile && yarn build
The yarn build
is defined as follows in the scripts of package.json.
esbuild ./index.ts --bundle --outfile=./index.js --platform=node --minify --allow-overwrite
Startup commands
Start the transpiled js file with the node command.
node app/index.js
Finally
Since I created a Go version of App Runner's Managed Runtime, I thought it would be interesting to write it in TypeScript as well.
It was a good experience for me to learn the difference between TypeScript and Go CDK.
Posted on June 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.