AWS Lambda入門③(Node編)〜API Gatewayで関数を公開する〜

## 概要

- [AWS Lambda入門①(Node編)〜関数をデプロイして動かす〜](https://qiita.com/ozaki25/items/883aec0ab4398aa4abbe)と[AWS Lambda入門②(Node編)〜DynamoDBにアクセスする〜](https://qiita.com/ozaki25/items/d3c5b5e6954d5b4af95a)の続編です
- 今回は前回までに作成したLambda関数をAPI Gatewayを使ってHTTPでアクセスできるように公開します

## Api Gatewayとは

- API GatewayはAWSが提供するサービスでHTTPでアクセス可能なAPIを作成できるサービスです
- AWSの様々なサービスの呼び出しができるのでクライアントからするとLambdaなどをHTTPアクセスで実行するような感覚で使うことができます

## API GatewayでLambda関数を公開する

### Hello関数を公開してみる

- まずは前回までで作ってあるHello関数を公開してみます
- `serverless.yml`に設定を追加します

```yml:serverless.yml
# ...省略

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

# ...省略
```

- `handler`の下にいくつか追加しました
    - `path`はアクセスするときのpathです
    - `method`はアクセスするときのHTTPメソッドです
- 簡単ですね！

### ローカルで動作確認

- デプロイする前にまずはローカルで動作確認します
- API Gatewayをローカルで動かすために必要なライブラリを追加します

```sh
yarn add -D serverless-offline@next
```

> serverless-offlineのドキュメントは2020/3/5時点でα版の6系で書かれているとのことなので`@next`をつけて6系をインストールする

- `serverless.yml`に設定を追加します

```yml:serverless.yml
# ...省略
plugins:
  - serverless-offline # 追加した行
  - serverless-dynamodb-local
custom:
  serverless-offline: # 追加した行
    httpPort: 8083 # 追加した行
  dynamodb:
    stages: dev
    start:
      port: 8082
      inMemory: true
      migrate: true
      seed: true
    seed:
      hello:
        sources:
          - table: ${self:provider.environment.DYNAMODB_TABLE}-hello
            sources: [./seeds/hello.json]
# ...省略
```

- `plugins`に`serverless-offline`を追加しました
- `custom`にサーバ起動時のポート番号を追加しました
    - デフォルトは3000です
- 以下のコマンドでサーバを起動します

```sh
sls offline start
```

- ログにURLが表示されているはずです

```
   ┌───────────────────────────────────────────┐
   │                                           │
   │   GET | http://localhost:8083/dev/hello   │
   │                                           │
   └───────────────────────────────────────────┘
```

- 今回は[REST Client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo/details?hl=ja-JP)を使って動作確認します

<img width="839" alt="スクリーンショット 2020-03-06 1.31.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/ca709a30-46f1-6b01-3157-a93f881affaf.png">

- うまくいけば画像のような結果になっているはずです
- Lambdaを直接叩いていた時は`statusCode`と`message`の2つが返ってきていましたが今回は`message`だけです
- `statusCode`はAPI GatewayがHTTPレスポンスのステータスコードとしてセットして返却してくれています

### AWSにデプロイして動作確認

- ローカルで確認できたのでAWSにデプロイします
- いつも通りServerlessFrameworkのコマンドを使います

```sh
sls deploy
```

- デプロイに成功すると作成されたAPIのURLがログに出力されます
    - こんな感じ

```
Serverless: Stack update finished...
Service Information
service: sls-sample
stage: dev
region: ap-northeast-1
stack: sls-sample-dev
resources: 24
api keys:
  None
endpoints:
  GET - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
functions:
  hello: sls-sample-dev-hello
  put: sls-sample-dev-put
  get: sls-sample-dev-get
  query: sls-sample-dev-query
  getAll: sls-sample-dev-getAll
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
✨  Done in 351.49s.
```

- REST Clientでアクセスして確認します

<img width="847" alt="スクリーンショット 2020-03-06 1.34.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/191eb8f9-8a01-1656-68af-b8976bdf7a2e.png">

- ステータスコード200で成功しました
- これでhello関数を世の中に公開することができました

### 全ての関数を公開する

- hello関数と同じように残りの関数も公開してみましょう

#### API Gatewayの設定追加

- `serverless.yml`を修正します

```yml:serverless.yml
# ...省略

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
  put:
    handler: handler.put
    environment:
      tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
    events:
      - http:
          path: messages
          method: post
  get:
    handler: handler.get
    environment:
      tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
    events:
      - http:
          path: messages/{id}
          method: get
  getAll:
    handler: handler.getAll
    environment:
      tableName: ${self:provider.environment.DYNAMODB_TABLE}-hello
    events:
      - http:
          path: messages
          method: get
# ...省略
```

- `function`の項目にそれぞれURLとHTTPメソッドを追加しました
    - getのpathは`messages/{id}`とすることでidをURLで指定するようにしました
- 関数を一部修正します
    - get関数がURLに埋め込まれたidを取得できるようする
    - put関数が渡されたパラメータを取得できるようにする

```js:handler.js
// ...省略
module.exports.get = async event => {
  const { id } = event.pathParameters; // この行を修正
  const params = {
    TableName: tableName,
    Key: { id },
  };

  try {
    const result = await dynamo.get(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify(result.Item),
    };
  } catch (error) {
    return {
      statusCode: error.statusCode,
      body: error.message,
    };
  }
};
// ...省略
module.exports.put = async event => {
  const id = String(Date.now());
  const { message } = JSON.parse(event.body); // この行を修正

  const params = {
    TableName: tableName,
    Item: { id, message },
  };

  try {
    await dynamo.put(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify({ id, message }),
    };
  } catch (error) {
    return {
      statusCode: error.statusCode,
      body: error.message,
    };
  }
};
// ...省略
```

#### ローカルで動作確認

- ローカルサーバを立ち上げます
    - ローカルのDBを見に行くように`LOCAL=true`をつけて環境変数を設定しておきます
    - 詳しくは[前回のDynamo編](https://qiita.com/ozaki25/items/d3c5b5e6954d5b4af95a#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%81%A7db%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%99%E3%82%8B)をご参照ください

```sh
LOCAL=true sls offline start
```

- ログにURLが出力されます

```
   ┌────────────────────────────────────────────────────┐
   │                                                    │
   │   GET  | http://localhost:8083/dev/hello           │
   │   POST | http://localhost:8083/dev/messages        │
   │   GET  | http://localhost:8083/dev/messages/{id}   │
   │   GET  | http://localhost:8083/dev/messages        │
   │                                                    │
   └────────────────────────────────────────────────────┘
```

- REST Clientでアクセスしてみましょう
- それぞれ成功すると以下のようになるはずです

<img width="401" alt="スクリーンショット 2020-03-06 2.05.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/e742518d-576f-f14c-802b-aa7b8f59f7e3.png">
<img width="288" alt="スクリーンショット 2020-03-06 2.06.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/526b5992-cf62-7ef8-3e39-f1eacac5fa9c.png">
<img width="601" alt="スクリーンショット 2020-03-06 2.06.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175213/da5f7046-09de-33ed-3349-03dfe6558b35.png">

#### AWSで動作確認

- AWSにデプロイしてREST Clientでアクセスしてみましょう

```sh
sls deploy
```

- 関数の文だけURLが表示されます

```
endpoints:
  GET  - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
  POST - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/messages
  GET  - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/messages/{id}
  GET  - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/messages
functions:
  hello: sls-sample-dev-hello
  put: sls-sample-dev-put
  get: sls-sample-dev-get
  getAll: sls-sample-dev-getAll
```

- REST Clientでアクセスしてみてください
- キャプチャは省略しますがローカルのときと同じようにアクセスできていればOKです

## まとめ

- API Gatewayを使うことでHTTリクエストでLambda関数を実行できました
- ServerlessFrameworkを使うと簡単にAPI Gatewayの設定をすることができます
- ローカルで動作させるためのライブラリも整っているため開発もやりやすくていいですね