AWS CDK(v2): Lambda と APIGateway を連携させる

目次

よく使う組み合わせなのに、管理画面で設定するとややこしいのが Lambda と APIGateway なのではないかと思います。 これが簡単に書けるだけでもかなり嬉しいです。 また、Lambda を TypeScript で書く場合、TypeScript の Transpile や node modules の Install なども自動で行ってパッケージ化してくれるのでかなり使いやすいです。

事前準備

以下の準備がされているものとします。

Version

  • node: 16.13.1
  • aws-cdk: 2.2.0
  • tsc: 4.5.4

cowsay サンプルアプリケーション

npm の cowsay を使って、リクエストされた文字列を牛がしゃべるテキストにして返すアプリケーションを作ってみることにします。

 _____________
< Let's Start >
 -------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Setup CDK

まずディレクトリを作成して、cdk init --language typescript をします。 これで色々雛形が作成されます。

% mkdir cow_say
% cd cow_say
% cdk init --language typescript

lib/cow_say-stack.tsCowSayStack 用のファイルが作られるので、最終的にはここに色々書いていきます。

/// lib/cow_say-stack.ts
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
// import * as sqs from 'aws-cdk-lib/aws-sqs';

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

    // The code that defines your stack goes here

    // example resource
    // const queue = new sqs.Queue(this, 'CowSayQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

今回は先に Lambda Function を作ってから、Stask の構成を書いていきます。

Lambda Function を作る

cowsay を Install します。

% npm install cowsay

Lambda の Handler ファイルはどこに置いても良いのですが、 lib/lambda/main.ts に書くとします。 コード自体はシンプルです。 API Gateway からの GETリクエストの QueryString パラメータの textの値を cowsay で変換して そのまま返すというものになります。

/// lib/lambda/main.ts
import * as cowsay from "cowsay";

export const handler = async (event: any = {}): Promise<any> => {
  try {
    let statusCode = 200;
    const text = event.queryStringParameters?.text ?? "Hello!";
    const said = cowsay.say({ text: text });
    return { statusCode: statusCode, body: said };
  } catch (error) {
    console.log(error);
    return { statusCode: 500, body: JSON.stringify({ message: `${error}` }) };
  }
};

UnitTest

Lambda のテストの良い書き方はわからなかったですが、最初から jest が入っているので例えば以下のようにすると UnitTest を実行できます。

/// test/lambda/main.test.ts
import * as main from "../../lib/lambda/main";

test("call handler", async () => {
  const event = {
    queryStringParameters: { text: "Hello!" },
  };
  const response = await main.handler(event);
  expect(response.statusCode).toBe(200);
  console.log(response.body);
});

npm run test でも実行できますし、 VSCode だとクリックするだけで特定のテストを実行できます。

VSCodeからのテスト起動

テスト結果

Stack 構成を記述する

今回は、APIGateway から Lambda を呼べれば良いので以下のように書けば OK です。 ポイントは NodejsFunction という NodeJS 用のを使うと、TypeScript の Transpile や node module も含めたパッケージ化をよしなにやってくれるというところです。

/// lib/cow_say-stack.ts
import { Stack, StackProps } from "aws-cdk-lib";
import { LambdaIntegration, RestApi } from "aws-cdk-lib/aws-apigateway";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
import * as path from "path";

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

    // https://aws.amazon.com/jp/blogs/news/lambda-managed-by-cdk/
    // For only NodeJS, Golang and Python now
    const cowLambda = new NodejsFunction(this, "CowSay", {
      entry: path.join(__dirname, "./lambda/main.ts"),
      handler: "handler",
    });

    // setup API Gateway for aaLambda
    const api = new RestApi(this, "CowSayAPI", {
      restApiName: "CowSayAPI",
    });

    const integration = new LambdaIntegration(cowLambda);
    api.root.addMethod("GET", integration);
  }
}

Stack を Deploy する

% cdk deploy

とすると、数分でデプロイされます。

実行してみる

curl で実行してみます。

% curl "https://umh9ezyai2.execute-api.ap-northeast-1.amazonaws.com/prod/?text=Hey"
{"message":"Error: ENOENT: no such file or directory, open '/var/cows/default.cow'"}

すると、エラーが出てしまいました。 /var/vows/default.cow というファイルが無い、と。 import でエラーが出ているわけではないのでどうやら cowsay の JS ファイルはパッケージに含まれているが、default.cow というリソースファイルが含まれていないようです。

そこで bundling.nodeModules Option で、cowsay を追加してみます。

/// lib/cow_say-stack.ts
...

    const cowLambda = new NodejsFunction(this, "CowSay", {
      entry: path.join(__dirname, './lambda/main.ts'),
      handler: "handler",
      // Add This!
      bundling: {
       nodeModules: ["cowsay"],
      }
    });

...

もう一度デプロイします。Lambda Function だけ更新したい場合は --hotswap という Option をつけると高速に更新できます。 ただし、あまり動作保証しないので、開発環境とかだけにしなさいと書かれています。

cdk deploy --hotswap

もう一度、curl でアクセスしてみます。

% curl "https://umh9ezyai2.execute-api.ap-northeast-1.amazonaws.com/prod/?text=Hey"
 _____
< Hey >
 -----
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

今度は無事に表示されました。

さいごに

Lambda と API Gateway を使うアプリケーションを配備してみました。 最初は少し大変かもしれませんが、慣れるとちゃちゃっと作れますし、全てがコードで管理できるのでメンテやチームでの運用も楽です。 こういうのは、実際に手を動かすといろいろ理解が進みますね。