AWS SAMでの Lambda Functions with Node.js + API Gateway 最小構成

AWS SAMを使ってLambda + API Gatewayを試しました。Lambdaの言語環境はNode.jsです。

# SAMのCLIツールをインストール

[Installing the AWS SAM CLI on Linux - AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html)

これを読むとLinuxにHomebrewを入れろとなっていますが、そこまではしたくないので、`pip`で入れます。私の環境はいまは`pipenv`を使うようにしていますので、コマンドは以下のようになります。

空のディレクトリにて、

```
$ pipenv install aws-sam-cli

$ pipenv run sam --version
SAM CLI, version 0.37.0
```

# samコマンドでひな型を作成

`sam init`というコマンドを実行すると、いくつか質問されるのでそれに答えると、ひな型となるファイル一式を生成してくれます。ディレクトリを1つ掘ってくれます。

以下は例です。Project nameは適当です。`sam`は`pipenv`でインストールしましたので、コマンドの前に`pipenv run`を付けています。

```
$ pipenv run sam init --runtime nodejs12.x
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Project name [sam-app]: hellonodealambda

Allow SAM CLI to download AWS-provided quick start templates from Github [Y/n]: Y

-----------------------
Generating application:
-----------------------
Name: hellonodealambda
Runtime: nodejs12.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./hellonodealambda/README.md
```

すると、以下のようなファイル構成になります。

```
$ find . -type f
./hellonodealambda/events/event.json
./hellonodealambda/README.md
./hellonodealambda/packaged.yaml
./hellonodealambda/hello-world/app.js
./hellonodealambda/hello-world/package.json
./hellonodealambda/hello-world/.npmignore
./hellonodealambda/hello-world/tests/unit/test-handler.js
./hellonodealambda/.gitignore
./hellonodealambda/template.yaml
./Pipfile
./Pipfile.lock
```

`Pipfile*`は`sam`をインストールしたときのファイルで、`pipenv`が必要とするだけです。Project nameのディレクトリ（`hellonodealambda`）が`sam init`で生成されたファイルです。これ以降は`hellonodealambda`ディレクトリ内で作業します。

```
$ cd hellonodealambda
```

# AWSにデプロイ

`sam package`を実行してCloudFormationのテンプレートファイルを作成します。`aws cloudformation package`に相当するものだと思います。これによりLambdaにデプロイするソース一式が一時的なS3にアップロードされ、アップロード先を参照する`packaged.yaml`が作成されます。

参考
[AWS CloudFormationをちょっとだけ理解した - Qiita](https://qiita.com/suzuki-navi/items/60c046c95c217487e4f4)

コマンドはこんな感じ。

```
$ pipenv run sam package --profile AWS_CREDENTIAL_PROFILE_NAME --template-file template.yaml --output-template-file packaged.yaml --s3-bucket MYBUCKET_NAME --s3-prefix hellonodealambda
```

次に`sam deploy`を実行して、CloudFormationにデプロイし、関連するAWSリソースを作成します。`aws cloudformation deploy`に相当します。

```
$ pipenv run sam deploy --profile AWS_CREDENTIAL_PROFILE_NAME --region ap-northeast-1 --template-file packaged.yaml --stack-name hellonodealambda --capabilities CAPABILITY_IAM
```

`--region`が必要なときとそうでないときがある気がするのですが、詳細はよくわかりません。`~/.aws/config`にregionを指定しているつもりなのですが、`sam deploy`のときにこれがないと私のアカウントでは `botocore.exceptions.NoRegionError: You must specify a region.` というエラーになってしまいました。

`--capabilities CAPABILITY_IAM`の意味もわかっていないです。これがないと

```
Error: Failed to create changeset for the stack: hellonodealambda, Parameter validation failed:
Invalid type for parameter Capabilities, value: None, type: <class 'NoneType'>, valid types: <class 'list'>, <class 'tuple'>
```

というエラーになってしまいました。

マネジメントコンソールのAPI GatewayでResourcesのページで、Actionsの中にあるDeploy APIというのをしますと、APIアクセスできるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/84974e38-e39e-d787-ae44-57ec253cad63.png)

```
$ curl 'https://XXXX.execute-api.ap-northeast-1.amazonaws.com/Stage/hello'
{"message":"hello world"}
```

# GETパラメータを受け取る

APIとしてGETパラメータをLambdaが受け取れるようにします。

`app.js`の中身を以下のようにしてみます。APIで渡したGETパラメータをログとレスポンスの両方に含めています。

```javascript
        console.log(event.queryStringParameters);
        console.log(event.multiValueQueryStringParameters);
        response = {
            'statusCode': 200,
            'body': JSON.stringify({
                message: 'hello world',
                params: event.queryStringParameters,
                multiParams: event.multiValueQueryStringParameters,
            })
        }
```

実行してみます。

```
$ curl 'https://XXXX.execute-api.ap-northeast-1.amazonaws.com/Stage/hello?pa=A&pa=AA&pb=BB'
{"message":"hello world","params":{"pa":"AA","pb":"BB"},"multiParams":{"pa":["A","AA"],"pb":["BB"]}}
```

同じ名前のパラメータを複数渡すと、`event.queryStringParameters`にはあとのものが優先され、`event.multiValueQueryStringParameters`には配列形式で全部が含まれるようです。

以上。