[AWS] Lambda + API Gateway でサーバーレスWeb APIを作る ①Lambdaファンクション篇



*2021/03時点での情報です。AWSのコンソール画面や仕様は頻繁に変更されるので注意してください。*

AWSのサービスであるLambdaとAPI Gatewayを使ってサーバーレスのWeb APIを作成したので備忘録として手順をまとめておきます。
間違えていたら教えていただけるとありがたいです。
  
# ゴール
AWS LambdaとAmazon API Gatewayを利用して**HTTPプロトコルのGET／POSTで呼び出せる単純なWeb API**を作成すること。  

# やらないこと
- AWS自体や他のサービスの説明
- 最低限のコード以外の実装
  
## APIの仕様
パラメーターの値を簡単に加工して返すだけのシンプルなAPIです。

  
**GET/POST共通**  

|パラメーター名|値|
|---|---|
|name|名前を入力します|
|age|年齢を入力します。|

レスポンス

```json
// nameとageにはパラメーターで渡した値が代入されます。
{
  "answer": "My name is ${name}, I am ${age} years old."
}
```


# AWS Lambdaとは
>AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。コードを ZIP ファイルまたはコンテナイメージとしてアップロードするだけで、Lambda はあらゆる規模のトラフィックに対して、自動的かつ正確にコンピューティング実行能力を割り当て、受信リクエストやイベントに基づいてコードを実行します。コードは、140 の AWS のサービスから自動的にトリガーするよう設定することも、ウェブやモバイルアプリケーションから直接呼び出すよう設定することもできます。Lambda 関数をお気に入りの言語 (Node.js、Python、Go、Java など) で記述し、サーバーレスツールと AWS SAM や Docker CLI などのコンテナツールの両方を使用して、関数をビルド、テスト、デプロイできます。（[公式より](https://aws.amazon.com/jp/lambda/)）
  
**だいたいこんな感じ**

  
- サーバーを建てないでコード（関数）を実行できるよ
- コードはいろんな言語で書けるよ
- 勝手に適切なCPUリソースを割り当ててくれるよ
- アプリケーションや他のAWSのサービスから作成した関数を呼び出せるよ

単一の関数をアップロードして独立して実行できるサービスです。

# Amazon API Gatewayとは
>フルマネージド型サービスの Amazon API Gateway を利用すれば、開発者は規模にかかわらず簡単に API の作成、公開、保守、モニタリング、保護を行えます。API は、アプリケーションがバックエンドサービスからのデータ、ビジネスロジック、機能にアクセスするための「フロントドア」として機能します。API Gateway を使用すれば、リアルタイム双方向通信アプリケーションを実現する RESTful API および WebSocket API を作成することができます。API Gateway は、コンテナ化されたサーバーレスのワークロードやウェブアプリケーションをサポートします。

**だいたいこんな感じ**

- 外部からHTTPプロトコルを通じてサービスを呼び出せるURLを作成してくれるよ
- 受け取ったリクエストを他のサービスに渡してくれるよ
- バージョン管理やモニタリングとか色々できるよ

# 今回の実装部分
## Lambdaファンクションの作成・コードの実装
Lambdaで作成する関数の単位はファンクションです。
ファンクションを作成してコードを実装（もしくはアップロード）します。

### ファンクション作成

Lambdaサービスのページから `Functions` > `Create Function`をクリック
![Screen Shot 2021-03-02 at 0.19.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/6cd22b89-074f-f702-fe1e-cc99fc583b25.png)

- `Author from scratch`を選択
- `Function name`にファンクション名を作成
- `Runtime`でコードを書く言語を指定（今回はNode.jsで書きます）
- `Create Function`をクリック

![Screen Shot 2021-03-02 at 0.38.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/73d643ab-a1a1-8685-aad7-0054f82e87a5.png)

これでファンクションが作成できたので中身を実装していきます。

### ファンクションのコード実装
今回はコンソールで直接コードを書きます。

`Designer`でファンクション名を選択してください。
![Screen Shot 2021-03-02 at 0.41.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/bb77a917-401c-fa51-5d40-1d59cc4330fc.png)

すると下に`Function code`エリアが現れるのでここにコードを書いていきます。
![Screen Shot 2021-03-02 at 0.44.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/5714baa2-9368-7370-377c-4bd7f6092257.png)


### GETの（クエリパラメーターを受け取る）場合の実装
HTTPリクエストをGETで受け取った場合、API Gatewayから渡される`event`変数の`queryStringParameters`にクエリパラメーターが格納されています。
なのでまずはGETリクエストを受け取った際のコードを実装してみます。

```js
exports.handler = async (event) => {
    const name = event.queryStringParameters.name
    const age = event.queryStringParameters.age

    return {
        "statusCode": 200,
        "headers": {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET"
        },
        "body": `{ "answer": "My name is ${name}, I am ${age} years old." }`,
        "isBase64Encoded": false
    }
}
```

ポイントは先程言ったとおり`event.queryStringParameters`からパラメーターが取得できるというところと、レスポンスの形式です。

後ほど紹介しますが、API GatewayとLambdaの連携方法として`Lambdaプロキシ統合`という方法を使用します。
この方法で連携する場合にはレスポンスは以下の形式で返す必要があります。

```json
{
  "isBase64Encoded" : "boolean",
  "statusCode": "number",
  "headers": { ... },
  "body": "JSON string"
}
```

### デプロイ・テスト
コードを実装したら`Deploy`を押してください。
コードがデプロイされて実行可能になります。

それではテストパラメーターを設定してテストを行います。
`Test`ボタンを押します。

![Screen Shot 2021-03-02 at 1.22.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/e1741e5e-15b2-7db3-c4c4-6d6756e50d96.png)

- Configure test eventが開きます。

- `Create new test event`を選択してイベント名を指定、パラメーターを入力します。（スクショではすでに作成済みなので`Edit saved test events`が選択されています。）

`プロキシ統合`の形式に合わせてパラメーターを指定します。

```json:success
{
    "queryStringParameters": {
        "name": "Akumachan",
        "age": "100030"
    }
}
```

`Create`を押してポップアップが閉じたら再度`Test`を押してテストを実行します。

![Screen Shot 2021-03-02 at 1.36.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/69dd122e-fdfb-e2f6-73d6-00e8dc84a268.png)

実行結果が表示されました。イージー！

### POSTの（リクエストボディを受け取る）場合の実装

HTTPリクエストをPOSTで受け取った場合、API Gatewayから渡される`event`変数の`body`にクエリパラメーターが格納されています。
`event.body`は文字列形式で格納されているようなのでJSONでリクエストボディを送信している場合は`JSON.parse`してパラメーターにアクセスします。

と、その前にメソッドによって処理を変えるコードを挿入します。
メソッドの種類は`event.httpMethod`に格納されているのでメソッドごとに分岐します。

**※(2021/03/06)下記コード修正しました。API GatewayでHTTP APIを作成した場合とREST APIを作成した場合で`event`内のHTTPメソッドが書いてある場所が違いました。HTTP APIの場合は`event.requestContext.http.method`で、REST APIの場合は`event.httpMethod`のようです。**  

```js:index.js
exports.handler = async (event) => {
    let name, age

    if ([event.httpMethod, event.requestContext.http.method].includes('GET')) {
        name = event.queryStringParameters.name
        age = event.queryStringParameters.age

    } else if ([event.httpMethod, event.requestContext.http.method].includes('POST')) {
        // Return response for POST request
    } else {
        // Return error.
    }

    return {
        statusCode: 200,
        body: `{ "answer": "My name is ${name}, I am ${age} years old." }`,
    }
}
```

それではPOSTリクエストボディからパラメーターを取り出して`name`と`age`に代入します。

```js:index.js
exports.handler = async (event) => {
    let name, age

    if ([event.httpMethod, event.requestContext.http.method].includes('GET')) {
        name = event.queryStringParameters.name
        age = event.queryStringParameters.age

    } else if ([event.httpMethod, event.requestContext.http.method].includes('POST')) {
        const body = JSON.parse(event.body)
        name = body.name
        age = body.age
    } else {
        // Return error.
    }

    return {
        statusCode: 200,
        body: `{ "answer": "My name is ${name}, I am ${age} years old." }`,
    }
}
```

レスポンスの形式は共通のコードなので、これでテストを実行してみましょう。`Deploy`を押してデプロイを完了してください。

### テスト
`Test`の横の▼をクリックして`Configure test event`をクリック、`Create new test event`からパラメーターを指定します。

```json:successPOST
{
  "httpMethod": "POST",
  "body": "{\"name\": \"AkumachanPOST\",\"age\": \"100032\"}"
}
``` 

`Create`ボタンを押してポップアップが閉じたら`Test`をクリックして実行します。

![Screen Shot 2021-03-03 at 22.53.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542817/80185782-6969-4686-64b0-849e3f975176.png)

これでPOSTリクエストからのリクエストも処理できるようになりました。

次回に続く。