Build TypeScript Managed App Runner with CDK
Tags: aws, cdk, typescript, apprunner
Published: 2023-06-24 02:36:09

## Overview

In [my last article](https://dev.to/aws-builders/build-golang-managed-app-runner-with-cdk-for-go-edd), 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](https://github.com/go-to-k/ts-cdk-ts-managed-apprunner).

---

## 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**.
- 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.)

```typescript
    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

```typescript
    const autoScalingConfigurationProvider = new Provider(
      this,
      "AutoScalingConfigurationProvider",
      {
        onEventHandler: customResourceLambda,
      },
    );

    // ...

    const autoScalingConfiguration = new CustomResource(this, "AutoScalingConfiguration", {
      resourceType: "Custom::AutoScalingConfiguration",
      properties: autoScalingConfigurationProperties,
      serviceToken: autoScalingConfigurationProvider.serviceToken,
    });
```

- Lambda code

```typescript
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.

```sh
#!/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).

```sh
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.

```sh
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.