Closed18

AWS CDK チュートリアル with TypeScript

shogoggshogogg

AWS CDK を長らく使っているけど、改めてゼロからプロジェクトを作る手順を整理したいと思う。

とりあえず API Gateway + Lambda で Web API を作るまで。以下構成図。

AWS 構成図

shogoggshogogg

Node.js のバージョンを確認し、プロジェクトを作成する。cdk コマンドを別途インストールするのは面倒なので npx を使って実行する。

$ node --version
v22.9.0

$ mkdir aws-cdk-example && cd aws-cdk-example
$ npx cdk init --language typescript

特に何も入力を求められることなく cdk init は終了する。作成された package.json の中身はこんなカンジ。

package.json
{
  "name": "aws-cdk-example",
  "version": "0.1.0",
  "bin": {
    "aws-cdk-example": "bin/aws-cdk-example.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
  "devDependencies": {
    "@types/jest": "^29.5.12",
    "@types/node": "22.5.4",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.5",
    "aws-cdk": "2.162.0",
    "ts-node": "^10.9.2",
    "typescript": "~5.6.2"
  },
  "dependencies": {
    "aws-cdk-lib": "2.162.0",
    "constructs": "^10.0.0",
    "source-map-support": "^0.5.21"
  }
}

おもしろいのは最初から git リポジトリとして作成されており、最初のコミット(Initial commit)が既にされている点。

shogoggshogogg

jest ではなく vitest を使いたいので jest 関連パッケージを削除して vitest を追加する。

https://vitest.dev/

$ npm uninstall jest ts-jest @types/jest

removed 287 packages, and audited 68 packages in 501ms

3 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

$ npm install --save-dev vitest

added 40 packages, and audited 108 packages in 2s

15 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

npm testvitest が実行されるように package.json を書き換える。

package.json
-    "test": "jest",
+    "test": "vitest run",
+    "test:watch": "vitest watch",
shogoggshogogg

.env を使いたいので dotenv をインストールする。

$ npm install --save dotenv

added 2 packages, and audited 110 packages in 396ms

16 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

※ node.js v20.6.0 以降は node.js 単体で .env の読み込みに対応しているっぽいが node コマンドのコマンドラインオプションの指定が必要など AWS CDK との相性が悪そうなので dotenv を用いることにした。

https://qiita.com/n0bisuke/items/c9f8cc3b7ddd419fcf1e

shogoggshogogg

AWS CDK を使う場合 npm run build.ts ファイルがビルドされて .js.d.ts ファイルが生成される。

これらを一括削除したい場合があるので rimraf を導入し、npm run clean で生成したファイルを削除できるようにする。

$ npm install --save-dev rimraf

added 41 packages, and audited 151 packages in 1s

29 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

package.jsonscripts に以下を追加する。

package.json
   "scripts": {
     "build": "tsc",
     "cdk": "cdk",
+    "clean": "rimraf {bin,lib}/*.{d.ts,js}",
     "test": "vitest",
     "watch": "tsc -w"
   },
shogoggshogogg

せっかくだから eslintprettier じゃなくて biome を使ってみたいのでインストールする。

https://biomejs.dev/ja/

$ npm install --save-dev --save-exact @biomejs/biome

added 2 packages, and audited 153 packages in 471ms

30 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

biome init を実行すると設定ファイルが生成される。--jsonc オプションを指定することで biome.json の代わりに biome.jsonc が生成される。

$ npx biome init --jsonc

Welcome to Biome! Let's get you started...

Files created

  - biome.jsonc
    Your project configuration. See https://biomejs.dev/reference/configuration

Next Steps

  1. Setup an editor extension
     Get live errors as you type and format when you save.
     Learn more at https://biomejs.dev/guides/integrate-in-editor/

  2. Try a command
     biome check  checks formatting, import sorting, and lint rules.
     biome --help displays the available commands.

  3. Migrate from ESLint and Prettier
     biome migrate eslint   migrates your ESLint configuration to Biome.
     biome migrate prettier migrates your Prettier configuration to Biome.

  4. Read the documentation
     Find guides and documentation at https://biomejs.dev/guides/getting-started/

  5. Get involved with the community
     Ask questions and contribute on GitHub: https://github.com/biomejs/biome
     Seek for help on Discord: https://discord.gg/BypW39g6Yc

生成される biome.jsonc の内容は以下の通り。

biome.jsonc
{
  "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
  "vcs": {
    "enabled": false,
    "clientKind": "git",
    "useIgnoreFile": false
  },
  "files": {
    "ignoreUnknown": false,
    "ignore": []
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "tab"
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double"
    }
  }
}

今回は以下のように設定した。

biome.jsonc
{
  "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
  "vcs": {
    // Biome の VCS 統合を有効にする
    "enabled": true,
    "clientKind": "git",
    // .gitignore に記述されたファイルを無視する
    "useIgnoreFile": true
  },
  "files": {
    "include": ["bin/**/*.ts", "lib/**/*.ts", "test/**/*.ts", "*.json", "*.jsonc"],
    "ignore": ["**/*.d.ts"]
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 120
  },
  "organizeImports": {
    "enabled": true
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "semicolons": "asNeeded"
    }
  }
}

最後に npm run checkbiome check が実行されるようにする。

package.json
     "cdk": "cdk",
+    "check": "biome check --write",
+    "check:dry-run": "biome check",
     "clean": "rimraf {bin,lib}/*.{d.ts,js}",
shogoggshogogg

husky + lint-staged を導入してコミット時に biome check が行われるようにする。

https://typicode.github.io/husky/
https://github.com/lint-staged/lint-staged

$ npm install --save-dev husky lint-staged

added 49 packages, and audited 202 packages in 3s

58 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

.lintstagedrc.yaml を以下の通り作成する。

.lintstagedrc.yaml
---
'*.{js,ts,json,jsonc}':
  - npm run check

husky init を実行し、生成された .husky/pre-commit を以下の通り編集する。

.husky/pre-commit
-npm test
+npx lint-staged
shogoggshogogg

いよいよ本題。まずは Route53 の Hosted Zone(ホストゾーン)を追加する。

まず最初に lib/aws-cdk-example-stack.ts を削除し、新たに lib/lambda-api-stack.ts を以下の通り作成する。

lib/lambda-api-stack.ts
import * as cdk from 'aws-cdk-lib'
import type { Construct } from 'constructs'

interface LambdaApiStackProps extends cdk.StackProps {
  hostedZoneName: string
}

export class LambdaApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: LambdaApiStackProps) {
    super(scope, id, props)

    // TODO: 実装する
  }
}

続いてテストを書く。先程同様に test/aws-cdk-example.test.ts を削除し test/lambda-api-stack.test.ts を以下の通り作成する。

test/lambda-api-stack.test.ts
import * as cdk from 'aws-cdk-lib'
import { Template } from 'aws-cdk-lib/assertions'
import { beforeAll, describe, it } from 'vitest'
import { LambdaApiStack } from '../lib/lambda-api-stack'

describe('LambdaApiStack', () => {
  let template: Template

  beforeAll(() => {
    const app = new cdk.App()
    const stack = new LambdaApiStack(app, 'TestStack', {
      hostedZoneName: 'example.org',
    })
    template = Template.fromStack(stack)
  })

  it('has hosted zone', () => {
    template.resourceCountIs('AWS::Route53::HostedZone', 1)
  })

  it('has hosted zone with the specified name', () => {
    template.hasResourceProperties('AWS::Route53::HostedZone', {
      Name: 'example.org.',
    })
  })
})

npm test を実行し、テストが落ちることを確認する。

$ npm test

> aws-cdk-example@0.1.0 test
> vitest run


 RUN  v2.1.2 /Users/shogogg/work/aws-cdk-example

 ❯ test/lambda-api-stack.test.ts (2)
   ❯ LambdaApiStack (2)
     × has hosted zone
     × has hosted zone with the specified name

準備が整ったので LambdaApiStack に Hosted Zone を追加する。lib/lambda-api-stack.ts に以下の記述を追加する。

lib/lambda-api-stack.ts
 export class LambdaApiStack extends cdk.Stack {
   constructor(scope: Construct, id: string, props: LambdaApiStackProps) {
     super(scope, id, props)
+    const { hostedZoneName } = props
+
+    // Route 53: Hosted Zone
+    new route53.HostedZone(this, 'HostedZone', {
+      zoneName: hostedZoneName,
+    })
   }
 }

改めてテストを実行すると今度は無事に通過する。

$ npm test

> aws-cdk-example@0.1.0 test
> vitest run


 RUN  v2.1.2 /Users/shogogg/work/aws-cdk-example

 ✓ test/lambda-api-stack.test.ts (2)
   ✓ LambdaApiStack (2)
     ✓ has hosted zone
     ✓ has hosted zone with the specified name

続いて bin/lambda-api-app.ts を以下の通り作成する。

bin/lambda-api-app.ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import * as dotenv from 'dotenv'
import { LambdaApiStack } from '../lib/lambda-api-stack'

// Load environment variables from .env file
dotenv.config()

const account = process.env.APP_AWS_ACCOUNT ?? process.env.CDK_DEFAULT_ACCOUNT
const region = process.env.APP_AWS_REGION ?? process.env.CDK_DEFAULT_REGION
const appName = process.env.APP_NAME ?? 'ExampleApp'
const hostedZoneName = process.env.APP_HOSTED_ZONE_NAME ?? 'example.com'

const app = new cdk.App()

new LambdaApiStack(app, `${appName}LambdaApiStack`, {
  env: {
    account,
    region,
  },
  hostedZoneName,
})
環境変数 CDK_DEFAULT_ACCOUNT と CDK_DEFAULT_REGION について

これらの環境変数は AWS CDK が「用意している」としている環境変数。今回は自前の環境変数が未定義の場合はそちらを参照するようにしている。

https://docs.aws.amazon.com/cdk/v2/guide/configure-env.html#configure-env-how-env

以下の通り .env ファイルを作成する。APP_HOSTED_ZONE_NAME には自身で DNS の設定が可能な適当なドメインを、APP_AWS_ACCOUNT には実際の AWS アカウント ID(12桁の数字)を指定する。

.env
APP_NAME=ExampleApp
APP_HOSTED_ZONE_NAME=************
APP_AWS_ACCOUNT=************
APP_AWS_REGION=ap-northeast-1

これで準備は完了。長くなったのでいったんここまで。

shogoggshogogg

前回作成したものをデプロイしていく。

まず最初に cdk bootstrap を実行し、AWS 環境に CDK を使ったデプロイに必要なリソース等を準備する。

cdk bootstrap

$ npx cdk bootstrap
MFA token for arn:aws:iam::************:mfa/********: ******
 ⏳  Bootstrapping environment aws://************/ap-northeast-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
CDKToolkit: creating CloudFormation changeset...
 ✅  Environment aws://************/ap-northeast-1 bootstrapped.

cdk bootstrap については以下の記事を読むと色々と詳しく書いてある。
https://www.cloudbuilders.jp/articles/3511/

build

続いてチュートリアルに従ってビルドを行う、と言いたいところだけど、その前に tsconfig.json をちょっといじる。

今回の構成ではテストに vitest を用いているため、test 以下のコードをビルドされると百害あって一利なし。なので excludetest を追加する。

tsconfig.json
-  "exclude": ["node_modules", "cdk.out"]
+  "exclude": ["node_modules", "cdk.out", "test"]

tsconfig.json の編集が済んだら今度こそビルドを行う。

$ npm run build

> aws-cdk-example@0.1.0 build
> tsc

cdk list

ビルドすると cdk list コマンドでスタックの一覧が表示できるようになる。

$ npx cdk list
ExampleAppLambdaApiStack

cdk synth

ビルド後に cdk synth を実行すると CloudFormation のテンプレートが cdk.out 以下に生成される。

$ npx cdk synth
Resources:
  HostedZoneDB99F866:
    Type: AWS::Route53::HostedZone
    Properties:
      Name: ****************.
    Metadata:
      aws:cdk:path: ExampleAppLambdaApiStack/HostedZone/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/zPSMzQz0jNUTCwv1k1OydbNyUzSqw4uSUzO1kksL44vyi8tSTU11qv2yC8uSU2Jys9L1XFOy0PwakHcoNTi/NKi5NRanbz8lFS9rGL9MiMjPUs9A8Ws4sxM3aLSvJLM3FS9IAgNAJXDNSNzAAAA
    Metadata:
      aws:cdk:path: ExampleAppLambdaApiStack/CDKMetadata/Default
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]

この cdk listcdk synth はデプロイするだけなら都度実行する必要はないんだけど、触ってみると CDK が何をしているのかがわかってちょっと面白い。

cdk deploy

いよいよデプロイ。cdk deploy コマンドを実行してデプロイを行う。

$ npx cdk deploy
MFA token for arn:aws:iam::************:mfa/********: ******

✨  Synthesis time: 7.22s

ExampleAppLambdaApiStack: start: Building 2ee87e0922900ea69049ccf5eda7714689d8af4fd6f998a367e8125exxxxxxxx:************-ap-northeast-1
ExampleAppLambdaApiStack: success: Built 2ee87e0922900ea69049ccf5eda7714689d8af4fd6f998a367e8125exxxxxxxx:************-ap-northeast-1
ExampleAppLambdaApiStack: start: Publishing 2ee87e0922900ea69049ccf5eda7714689d8af4fd6f998a367e8125exxxxxxxx:************-ap-northeast-1
ExampleAppLambdaApiStack: success: Published 2ee87e0922900ea69049ccf5eda7714689d8af4fd6f998a367e8125exxxxxxxx:************-ap-northeast-1
Stack undefined
ExampleAppLambdaApiStack: deploying... [1/1]
ExampleAppLambdaApiStack: creating CloudFormation changeset...

 ✅  ExampleAppLambdaApiStack

✨  Deployment time: 72.56s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:************:stack/ExampleAppLambdaApiStack/635d3fb0-xxxx-xxxx-xxxx-0ad8feee2f63

✨  Total time: 79.78s

無事にデプロイが完了した。AWS マネジメントコンソール から、以下を確認できる。

  • CloudFormation に ExampleAppLambdaApiStack が追加されていること。
  • Route 53 に指定した Hosted Zone が追加されていること。

cdk destroy

気軽にデプロイできる&破棄できるのが IaC のいいところ。という訳で今デプロイしたものを早速破棄してみる。

$ npx cdk destroy
MFA token for arn:aws:iam::************:mfa/********: ******
Are you sure you want to delete: ExampleAppLambdaApiStack (y/n)? y
ExampleAppLambdaApiStack: destroying... [1/1]

 ✅  ExampleAppLambdaApiStack: destroyed

これできれいさっぱり消えたはず。

shogoggshogogg

続いて Lambda 関数を追加する。まずはテスト。

test/lambda-api-stack.test.ts
+  it('has lambda function', () => {
+    template.resourceCountIs('AWS::Lambda::Function', 1)
+  })
+
+  it('has lambda function with the specified runtime', () => {
+    template.hasResourceProperties('AWS::Lambda::Function', {
+      Runtime: 'nodejs20.x',
+    })
+  })

テストが落ちることを確認したら LambdaApiStack に Lambda 関数を追加していく。まずは追加する関数本体を TypeScript で記述するため、@types/aws-lambda を依存パッケージに追加する。

$ npm install --save-dev @types/aws-lambda

added 1 package, and audited 203 packages in 449ms

58 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

次に lib/lambda-api-stack.app.ts を以下の通り作成。

lib/lambda-api-stack.app.ts
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  // クエリパラメータで指定された挨拶を用いる
  const greeting = event.queryStringParameters?.greeting ?? 'Hello'

  // 環境変数で指定された名前を用いる
  const name = process.env.APP_GREETING_NAME ?? 'World'

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: `${greeting}, ${name}!`,
    }),
  }
}

最後に LambdaApiStack に Lambda 関数の定義を追加する。

lib/lambda-api-stack.ts
 export class LambdaApiStack extends cdk.Stack {
   constructor(scope: Construct, id: string, props: LambdaApiStackProps) {
     super(scope, id, props)
     const { hostedZoneName } = props
 
+    // Lambda: Function
+    new lambdaNodejs.NodejsFunction(this, 'app', {
+      runtime: lambda.Runtime.NODEJS_20_X,
+    })
+
     // Route 53: Hosted Zone
     new route53.HostedZone(this, 'HostedZone', {
       zoneName: hostedZoneName,
     })
   }
 }

ここまで来ればテストが通るようになる。

$ npm test
shogoggshogogg

API Gateway の定義を追加する。まずはテスト。

test/lambda-api-stack.test.ts
+  it('has API Gateway REST API', () => {
+    template.resourceCountIs('AWS::ApiGateway::RestApi', 1)
+  })
+
+  it('has API Gateway Resource', () => {
+    template.resourceCountIs('AWS::ApiGateway::Resource', 1)
+  })
+
+  it('has API Gateway Resource with the specified path', () => {
+    template.hasResourceProperties('AWS::ApiGateway::Resource', {
+      PathPart: 'example',
+    })
+  })

テストが落ちることを確認したら LambdaApiStack に API Gateway の定義を追加していく。

lib/lambda-api-stack.ts
     // Lambda: Function
-    lambdaNodejs.NodejsFunction(this, 'app', {
+    const apiFunction = new lambdaNodejs.NodejsFunction(this, 'app', {
       runtime: lambda.Runtime.NODEJS_20_X,
     })
+
+    // API Gateway: Routing
+    const api = new apiGateway.RestApi(this, 'ApiGateway')
+    api.root.addResource('example').addMethod('GET', new apiGateway.LambdaIntegration(apiFunction))

テストの通過を確認したら次へ。

shogoggshogogg

API で用いるサーバー証明書を追加する。まずは Hosted Zone に追加する API 用のドメイン名を設定できるようにする。

lib/lambda-api-stack.ts
 interface LambdaApiStackProps extends cdk.StackProps {
   hostedZoneName: string
+  domainName: string
 }

型定義の変更に伴い、bin/lambda-api-app.ts および test/lambda-api-stack.test.ts にも修正を加える。

bin/lambda-api-app.ts
 const hostedZoneName = process.env.APP_HOSTED_ZONE_NAME ?? 'example.com'
+const domainName = process.env.APP_DOMAIN_NAME ?? 'api.example.com'

 const app = new cdk.App()

 new LambdaApiStack(app, `${appName}LambdaApiStack`, {
   env: {
     account,
     region
   },
   hostedZoneName,
+  domainName
 })
test/lambda-api-stack.test.ts
   beforeAll(() => {
     const app = new cdk.App()
     const stack = new LambdaApiStack(app, 'TestStack', {
       hostedZoneName: 'example.org',
+      domainName: 'api.example.org',
     })
     template = Template.fromStack(stack)
   })

準備が整ったので、まずはテストを追加する。

test/lambda-api-stack.test.ts
+  it('has ACM certificate', () => {
+    template.resourceCountIs('AWS::CertificateManager::Certificate', 1)
+  })
+
+  it('has ACM certificate with the specified domain name', () => {
+    template.hasResourceProperties('AWS::CertificateManager::Certificate', {
+      DomainName: 'api.example.org',
+    })
+  })

テストが落ちることを確認したら、LambdaApiStack に証明書の定義を追加していく。

lib/lambda-api-stack.ts
     super(scope, id, props)
+    const { hostedZoneName, domainName } = props
 
     // Lambda: Function
     const apiFunction = new lambdaNodejs.NodejsFunction(this, 'app', {
       runtime: lambda.Runtime.NODEJS_20_X,
     })
 
     // API Gateway: Routing
     const api = new apiGateway.RestApi(this, 'ApiGateway')
     api.root.addResource('example').addMethod('GET', new apiGateway.LambdaIntegration(apiFunction))
 
     // Route 53: Hosted Zone
-    new route53.HostedZone(this, 'HostedZone', {
+    const hostedZone = new route53.HostedZone(this, 'HostedZone', {
       zoneName: hostedZoneName,
     })
+
+    // ACM: Certificate
+    new certificationManager.Certificate(this, 'Certificate', {
+      domainName,
+      validation: certificationManager.CertificateValidation.fromDns(hostedZone),
+    })

テストの通過を確認したら次へ。

shogoggshogogg

次に API Gateway へカスタムドメインの設定を行う。まずはテストから。

test/lambda-api-stack.test.ts
+  it('has API Gateway Custom Domain', () => {
+    template.resourceCountIs('AWS::ApiGateway::DomainName', 1)
+  })
+
+  it('has API Gateway Custom Domain with the specified domain name', () => {
+    template.hasResourceProperties('AWS::ApiGateway::DomainName', {
+      DomainName: 'api.example.org',
+    })
+  })

テストが落ちることを確認したら LambdaApiStack を修正する。

lib/lambda-api-stack.ts
     // ACM: Certificate
-    new certificationManager.Certificate(this, 'Certificate', {
+    const certificate = new certificationManager.Certificate(this, 'Certificate', {
       domainName,
       validation: certificationManager.CertificateValidation.fromDns(hostedZone),
     })
+
+    // API Gateway: Custom Domain
+    const customDomain = new apiGateway.DomainName(this, 'CustomDomain', {
+      domainName,
+      certificate,
+      securityPolicy: apiGateway.SecurityPolicy.TLS_1_2,
+      endpointType: apiGateway.EndpointType.REGIONAL,
+    })
+    customDomain.addBasePathMapping(api)

テストの通過を確認したら次へ。

shogoggshogogg

定義はこれでラスト。Route 53 の Hosted Zone に API Gateway 用のレコードを追加する。まずはテスト。

test/lambda-api-stack.test.ts
+  it('has Route 53 RecordSet', () => {
+    template.resourceCountIs('AWS::Route53::RecordSet', 1)
+  })
+
+  it('has Route 53 RecordSet with the specified domain name', () => {
+    template.hasResourceProperties('AWS::Route53::RecordSet', {
+      Name: 'api.example.org.',
+    })
+  })
+
+  it('has Route 53 RecordSet with the specified type', () => {
+    template.hasResourceProperties('AWS::Route53::RecordSet', {
+      Type: 'A',
+    })
+  })

テストが落ちることを確認し、LambdaApiStack に定義を追加する。

lib/lambda-api-stack.ts
     customDomain.addBasePathMapping(api)
+
+    // Route 53: Record
+    new route53.ARecord(this, 'ARecord', {
+      zone: hostedZone,
+      recordName: domainName,
+      target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayDomain(customDomain)),
+    })

テストの通過を確認したら次へ。

shogoggshogogg

一通りリソースの定義ができたのでデプロイして動作を確認する。

$ npx cdk deploy
(略)

$ curl "https://api.example.com/example"
{"message": "Hello, World!"}

$ curl "https://api.example.com/example?greeting=Hi"
{"message": "Hi, World!"}

今回作成した Lambda 関数は環境変数でメッセージを変更できるようになっているので、変更してみる。LambdaApiStack に以下の通り追記。

lib/lambda-api-stack.ts
     // Lambda: Function
     const apiFunction = new lambdaNodejs.NodejsFunction(this, 'app', {
       runtime: lambda.Runtime.NODEJS_20_X,
+      environment: {
+        APP_GREETING_NAME: 'Lambda',
+      },
     })

再度デプロイ、実行してみる。

$ npx cdk deploy
(略)

$ curl "https://api.example.com/example"
{"message": "Hello, Lambda!"}

$ curl "https://api.example.com/example?greeting=Hi"
{"message": "Hi, Lambda!"}

期待通り動いた。今回はここまで。

shogoggshogogg

最終的な LambdaApiStack の全貌はこちら。

lib/lambda-api-stack.ts
import * as cdk from 'aws-cdk-lib'
import * as apiGateway from 'aws-cdk-lib/aws-apigateway'
import * as certificationManager from 'aws-cdk-lib/aws-certificatemanager'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'
import * as route53 from 'aws-cdk-lib/aws-route53'
import * as route53Targets from 'aws-cdk-lib/aws-route53-targets'
import type { Construct } from 'constructs'

interface LambdaApiStackProps extends cdk.StackProps {
  hostedZoneName: string
  domainName: string
}

export class LambdaApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: LambdaApiStackProps) {
    super(scope, id, props)
    const { hostedZoneName, domainName } = props

    // Lambda: Function
    const apiFunction = new lambdaNodejs.NodejsFunction(this, 'app', {
      runtime: lambda.Runtime.NODEJS_20_X,
      environment: {
        APP_GREETING_NAME: 'Lambda',
      },
    })

    // API Gateway: Routing
    const api = new apiGateway.RestApi(this, 'ApiGateway')
    api.root.addResource('example').addMethod('GET', new apiGateway.LambdaIntegration(apiFunction))

    // Route 53: Hosted Zone
    const hostedZone = new route53.HostedZone(this, 'HostedZone', {
      zoneName: hostedZoneName,
    })

    // ACM: Certificate
    const certificate = new certificationManager.Certificate(this, 'Certificate', {
      domainName,
      validation: certificationManager.CertificateValidation.fromDns(hostedZone),
    })

    // API Gateway: Custom Domain
    const customDomain = new apiGateway.DomainName(this, 'CustomDomain', {
      domainName,
      certificate,
      securityPolicy: apiGateway.SecurityPolicy.TLS_1_2,
      endpointType: apiGateway.EndpointType.REGIONAL,
    })
    customDomain.addBasePathMapping(api)

    // Route 53: Record
    new route53.ARecord(this, 'ARecord', {
      zone: hostedZone,
      recordName: domainName,
      target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayDomain(customDomain)),
    })
  }
}
このスクラップは2ヶ月前にクローズされました