AWS CDKでApi Gatewayを設定しLambdaから読み出してローカル実行を行ったメモ

## 概要
[前回はLambda Layerを導入した。](https://qiita.com/hibohiboo/items/b8b575d7194aef3d1b57)
今回はAPI Gatewayを使ってHTTPでLambda Functionsを叩けるようにする。

[ソースコード](https://github.com/hibohiboo/develop/tree/903dca887820b1fc29a33d41e9aae8c07aa1eaa1/tutorial/lesson/aws/typescript/projects/cdk_sample)


## やること
* ローカル実行
* POSTのテスト
* GETでパラメータを取得できることのテスト

## やらないこと
デプロイ

## コード
### GET用のFunctionsの追加

```ts:src/lambda/handlers/get-echo.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import * as response from '../utilities/reponse';

export const echoHandler: APIGatewayProxyHandler = async (event) => {
  const { httpMethod, path, pathParameters } = event;
  if (httpMethod !== 'GET') {
    throw new Error(`getMethod only accept GET method, you tried: ${httpMethod}`);
  }
  console.log('received:', JSON.stringify(event));

  // Get id from pathParameters from APIGateway because of `/{id}` at template.yml
  const params = pathParameters;
  if (!params || !params.id) {
    return response.badRequest('parameter is required')
  }
  const { id } = params;
  const res = response.ok(id);
  console.log(`response from: ${path} statusCode: ${res.statusCode} body: ${res.body}`);
  return res;
};
```

### Stack

```diff:lib/sample-stack.ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
+ import * as apigateway from '@aws-cdk/aws-apigateway'
import { NODE_LAMBDA_LAYER_DIR } from './process/setup';

export class SampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
-   new NodejsFunction(this, 'hello', {
+   const helloFunction = new NodejsFunction(this, 'hello', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: 'src/lambda/handlers/hello.ts',
      functionName: 'kotahello',
      handler: 'lambdaHandler'
    });

    const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer',
      {
        code: lambda.AssetCode.fromAsset(NODE_LAMBDA_LAYER_DIR),
        compatibleRuntimes: [lambda.Runtime.NODEJS_14_X]
      }
    );

    new NodejsFunction(this, 'test', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: 'src/lambda/handlers/test.ts',
      functionName: 'kotatest',
      bundling: {
        externalModules: [
          'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
          'date-fns', // Layrerに入れておきたいモジュール
        ],
        define: { // Replace strings during build time
          'process.env.API_KEY': JSON.stringify(`\\"${'xxx-xxx'}\\"`), // バグってそう.エスケープしないとInvalid define valueのエラー
        },
      },
      layers: [nodeModulesLayer],
    });
+    const echoFunction = new NodejsFunction(this, 'echo', {
+      runtime: lambda.Runtime.NODEJS_14_X,
+      entry: 'src/lambda/handlers/get-echo.ts',
+      functionName: 'get-echo',
+      handler: 'echoHandler'
+    });
+    const api = new apigateway.RestApi(this, 'ServerlessRestApi', { cloudWatchRole: false });
+    api.root.addMethod('POST', new apigateway.LambdaIntegration(helloFunction));
+    api.root.addResource('{id}').addMethod('GET', new apigateway.LambdaIntegration(echoFunction));
  }
}
```

### package.json

```diff:package.json
{
  "name": "myapp",
  "version": "0.1.0",
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk",
    "bundle": "cdk synth && cdk synth > cdk.out/template.yaml",
    "localinvoke": "cd cdk.out && sam.cmd local invoke testAF53AC38 --no-event",
+    "dev-server": "cd cdk.out && sam.cmd local start-api"
  },
  "devDependencies": {
    "@aws-cdk/assert": "^1.93.0",
+    "@aws-cdk/aws-apigateway": "^1.93.0",
    "@aws-cdk/aws-lambda-nodejs": "^1.93.0",
    "@aws-cdk/core": "^1.93.0",
    "@types/aws-lambda": "^8.10.72",
    "@types/fs-extra": "^9.0.8",
    "@types/jest": "^26.0.10",
    "@types/node": "10.17.27",
    "aws-cdk": "^1.93.0",
    "esbuild": "^0.9.0",
    "fs-extra": "^9.1.0",
    "jest": "^26.4.2",
    "source-map-support": "^0.5.19",
    "ts-jest": "^26.2.0",
    "ts-node": "^9.0.0",
    "typescript": "~3.9.7"
  },
  "dependencies": {
    "date-fns": "^2.19.0"
  }
}
```


## 結果
* `npm run dev-server`
* getもpostも想定通りの結果を返してくれた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/c49fb5d7-322a-b9dc-83b0-db59451151c7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/fbb78d13-3313-6ed0-9ea4-ecf0371a1fed.png)


## 参考

[AWS CDKでAPI Gateway+Lambdaを作成する際のベストなスタック構成について][*1]
[API Gatewayのローカルでの実行][*2]
[サーバーレスをこれから始める方へ！「形で考えるサーバーレス設計」のCDKテンプレート(web/mBaaS編)を試して解説してみた][*3]
[AWS CDKでTypeScriptで書かれたLambdaにLambda ProxyなREST APIを追加する][*4]
[@aws-cdk/aws-apigateway module][*5]

[*1]:https://dev.classmethod.jp/articles/apig-and-lambda-best-stack-configuration-with-aws-cdk/
[*2]:https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-start-api.html
[*3]:https://dev.classmethod.jp/articles/introduction-to-serverless-design-on-cdk/
[*4]:https://wp-kyoto.net/create-a-simple-typescript-rest-api-using-aws-cdk/
[*5]:https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigateway-readme.html