初めての、LambdaとDynamoDBを使ったAPI開発

# はじめに
LambdaとDynamoDB使ってAPI作ることになったんで、いろいろ試したメモ
詰まった点や、解決法など、参考になれば。（主に自分用のメモですが）
なおメソッドはPOSTでやっています。GETの記事が多かったので参考になれば。

# この記事でやること
- DynamoDBでテーブル作成
- Lambda上でDynamoDBの値を取得（select）
- Lambda上でDynamoDBに値を挿入(insert)
- Lambda上でテスト
- API Gatewayの設定
- curlによるリクエストとレスポンス

# DynamoDBでテーブル作成
テーブルを作ります。

<img width="949" alt="スクリーンショット 2018-01-18 10.22.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/297066d0-f1ed-7481-6329-687d028f81db.png">

「テーブルの作成」をクリックし

<img width="1098" alt="スクリーンショット 2018-01-18 10.22.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/4e417d1e-d20e-385d-5c3e-139aef76f052.png">

テーブル名とプライマリキー、必要であればソートキーを入力します。
DynamoDBはテーブル作成時にはPrimary KeyとSort Keyのみの指定であとはレコード挿入時に設定します。

## レコードをコンソール上から挿入する
レコードを挿入します。

<img width="799" alt="スクリーンショット 2018-01-18 10.27.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/d8a70952-925a-dbd3-5583-ec5c5c6d5e8f.png">

テーブルを選択し、「項目」タブ内にある「項目の作成」をクリックします。

<img width="581" alt="スクリーンショット 2018-01-18 10.27.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/cf05d7fc-08ab-21c7-25c5-2cb9001b6710.png">

この画面で画面左の＋ボタンを押すとカラムを追加できるので、ここでカラムを追加します。
なおカラムは永続的なものはプライマリキーとソートキーのみなので逐一設定する必要があります。

# Lambdaで関数を作成する

## IAMでDynamoDBにアクセス可能なロールを作成
Lambdaで関数を作成する際にロールを求められるので先に作成します。

<img width="1070" alt="スクリーンショット 2018-01-18 10.36.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/58056d43-199f-4e16-ad8f-fe86c3816505.png">

「ロールの作成」をクリック

<img width="950" alt="スクリーンショット 2018-01-18 10.38.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/8a6ae761-f96d-e126-67cf-0fb584c2c728.png">

Lambdaを選択

<img width="974" alt="スクリーンショット 2018-01-18 10.38.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/d9d6e278-0207-4c34-674b-58c42c5d32bd.png">

検索窓にdynamoなんちゃらと入れると絞り込まれるので、
AmazonDynamoDBFullAccess
AWSLambdaDynamoDBExecutionRole
をチェックし次へ

<img width="965" alt="スクリーンショット 2018-01-18 10.39.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/d32a5c91-17e8-c279-84ad-b65087bdfc97.png">

確認画面でロール名を入力し作成完了

## 関数の作成

<img width="1308" alt="スクリーンショット 2018-01-18 10.34.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/a3fcdcb2-abce-1275-ad4a-6dc3e474af6d.png">

「関数の作成」をクリック
どうでもいいですが、myZaifChartは[以前Alexaやった時](https://qiita.com/miutex/items/ff09b2f171be1588ce7b)に作ったものです。

<img width="886" alt="スクリーンショット 2018-01-18 10.48.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/9268de72-337a-463b-0521-4d4443ce347a.png">

「一から作成」を選択し
ロールは先ほど作成したものがプルダウンに表示されるのでそれを選択します。
名前は好きなものを。

入力したら「関数を作成」ボタンを押します。

## Lambda上でDynamoDBの値を取得

### scan
全取得するにはscan()を使います。

```javascript:index.js
var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB.DocumentClient({
    region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
    var params = {
        TableName: "hoge_user"
    };
    console.log("event:", event);
    dynamo.scan(params, function(err, data) {
        console.log("dynamo_data:", data);
        console.log("dynamo_err:", err);
        context.done(null, data);
    });
};
```

但し、scanは常にテーブル全体に読み取りが走ってしまうため、レコード数が増加し続けるテーブルに対しscanを行っていると毎月の費用がぐんぐん上昇していく羽目に遭います。
後述するgetやqueryをなるべく使うようにする方が賢明です。

### get

1件だけ取得したい場合はgetを使います。

```javascript:index.js
var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB.DocumentClient({
    region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
    var params = {
        TableName: "hoge_user",
        Key:{
            user_id: "20180117_01abcde",
            created_at: 1516170458
        }
    };
    console.log("event:", event);
    dynamo.get(params, function(err, data) {
        console.log("dynamo_data:", data);
        console.log("dynamo_err:", err);
        context.done(null, data);
    });
};
```

getは1件だけ取得するメソッドのため、**ソートキーまで設定している場合にはプライマリキーとソートキーの両方を引数に設定しないと取得できません。**
プライマリキーのみの設定の場合には引数は1つで大丈夫です。

### query

queryは1〜n件を取得するために使います。
getと似ていますがソートキーまで設定した場合にプライマリキーは重複して入力できますので、そういった場合には複数のレコードを取得することができます。

```javascript:index.js
var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB.DocumentClient({
    region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
    var params = {
        TableName: "hoge_user",
        KeyConditionExpression: "#key = :str",
        ExpressionAttributeNames:{
            "#key": "user_id"
        },
        ExpressionAttributeValues: {
            ":str": "20180117_01abcde"
        }
    };

    console.log("event:", event);
    dynamo.query(params, function(err, data) {
        console.log("dynamo_data:", data);
        console.log("dynamo_err:", err);
        context.done(null, data);
    });
};
```

## Lambda上でDynamoDBに値を挿入
こんな感じで

```javascript:index.js
var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB.DocumentClient({
    region: 'us-east-1'
});

exports.handler = (event, context, callback) => {
    
    var date = new Date();
    var time1 = date.getTime();
    var unixtime_sec = Math.floor(time1 / 1000);
    
    console.log("event:", event);
    dynamo.put({
        "TableName": "hoge_user",
        "Item": {
            "user_id": event.user_id,
            "device_token": event.device_token,
            "created_at": unixtime_sec,
            "updated_at": unixtime_sec
        }
    }, function( err, data ) {
        console.log("dynamo_err:", err);
        context.done(null, data);
    });
};
```

# Lambda上でテスト
こんな感じでテスト設定し動作を確認します。

<img width="806" alt="スクリーンショット 2018-01-18 13.11.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/fd4734bc-81d1-2086-515b-12f6d142180b.png">

**ここまで出来れば、Lambdaを使ってDynamoDBと連携するところまでは上手くいっています。**
ここからAPI Gatewayの部分を設定していきます。

# API Gatewayの設定
API Gatewayの設定はLambdaのコンソールだと見れる項目が少なすぎてさっぱりなので、
API Gatewayのコンソールから設定する方が良いです。
LambdaコンソールでAPI追加するとメソッドもANYになってしまい、これも混乱のもとです。

## APIの作成

<img width="688" alt="スクリーンショット 2018-01-18 13.15.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/b140c195-f9d5-fd5a-d061-a98567f11e30.png">

「APIの作成」をクリック

<img width="1002" alt="スクリーンショット 2018-01-18 13.16.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/3bf24faf-4a30-d3f0-bc55-c48dfaee770e.png">

「新しいAPI」を選択し、API名は好きなものを。エンドポイントタイプはデフォルトで選択されているエッジ最適化を使います。

<img width="856" alt="スクリーンショット 2018-01-18 13.16.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/0f5af8f9-d6e1-2b6e-ee87-82b5e6b12e65.png">

作るとこんな状態になります
「アクション」→「メソッドの作成」をクリックします。
中央のペインにプルダウンが表示されますので、ここではPOSTを選択します。

<img width="1022" alt="スクリーンショット 2018-01-18 13.17.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/c8c2ba4e-7b90-1f15-8be1-6ec357bbf05f.png">

統合タイプ：Lambda関数
Lambdaプロキシ統合の使用：オフ
Lambdaリージョン：Lambda関数を作ったリージョンを選択
→リージョン選択するとメソッドを指定するフォームが表示されるので、Lambdaで作った関数名を入力

<img width="1576" alt="スクリーンショット 2018-01-18 13.17.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/f9a0b5d5-e9d0-e693-ec5d-7a1f30c53582.png">

終わるとこんな画面になります。

「アクション」プルダウンから「APIのデプロイ」をクリック
ステージ名を聞かれるので新規に作成します。名称は好きなものを。

<img width="1577" alt="スクリーンショット 2018-01-18 13.18.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/fac3a566-d253-69f1-d8f3-14a1f22bd4fc.png">

終わるとこんな画面になります。

## curlでテスト
ここで一旦、curlでリクエストしてみましょう。

```console:console
$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' https://*********************.amazonaws.com/dev -d '{"user_id":"20180118_01abcde","device_token":"token20180118_01"}' --dump-header -
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 4
Connection: keep-alive
Date: Thu, 18 Jan 2018 04:21:12 GMT
x-amzn-RequestId: **************************
X-Amzn-Trace-Id: **************************
X-Cache: Miss from cloudfront
Via: 1.1 **************************.cloudfront.net (CloudFront)
X-Amz-Cf-Id: **************************
```

おっしゃあああ！できた！

## APIキーの設定
このままだとザルなのでAPIキーを設定します。
**APIキーは作成するだけだと効果がありません。
APIキー、使用量プラン、APIへの設定の3点セットで紐付けを行います。**

<img width="832" alt="スクリーンショット 2018-01-18 14.01.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/84358479-06be-d150-413b-a6fcf6ec8978.png">

左ペインでAPIキーを選択しすると上の画面になります。
「アクション」→「APIキーの作成」へ

<img width="1082" alt="スクリーンショット 2018-01-18 14.01.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/4c866069-8ff4-ef54-7493-dfb0460ebcf8.png">

名前は好きなものを
APIキーが作成されました。

## 使用量プランの設定
使用量プランと書いてありますが**課金設定ではありません**（課金を匂わす文言だとは思いますが）
左ペインの「使用料プラン」を選択し、表示された画面から「作成」ボタンをクリックします。

<img width="1158" alt="スクリーンショット 2018-01-18 14.02.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/24300718-8128-c95d-cc86-adee8a954911.png">

名前：好きなものを
スロットリングの有効化：オフ
クォータ：オフ

## APIステージの関連付け

<img width="1202" alt="スクリーンショット 2018-01-18 14.03.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/f8a245c7-4bed-8375-03d6-0d8916381d68.png">

ここでAPIとの紐付けを行います
「APIステージの追加」をクリックするとテーブルにプルダウンが表示されるので、
紐付けたいAPIとステージ（デプロイ時に作ったやつ）を選択します

## 使用料プランのAPIキー

<img width="1079" alt="スクリーンショット 2018-01-18 14.04.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/8486f70d-e751-4418-d055-084fc53c92cb.png">

ここで先ほど作ったAPIキーを設定します。

<img width="1291" alt="スクリーンショット 2018-01-18 14.05.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/e0d2bcb9-dd8a-1495-0491-4f4f8340d6d8.png">

メソッド側でAPIキーの必要性をTrueにします。
**ここまで終わったらデプロイしましょう。**デプロイしていないせいで思った通り動かないということがよくあります。

## curlでテスト
リクエストしてみましょう

```console:console
$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' https://****************************.amazonaws.com/dev -d '{"user_id":"20180118_02abcde","device_token":"token20180118_02"}' --dump-header -
HTTP/1.1 403 Forbidden
Content-Type: application/json
Content-Length: 24
Connection: keep-alive
Date: Thu, 18 Jan 2018 05:21:49 GMT
x-amzn-RequestId: ****************************
x-amzn-ErrorType: ForbiddenException
X-Cache: Error from cloudfront
Via: 1.1 ****************************.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ****************************

{"message":"Forbidden"}
```

はい、先ほどは200だったものが403になりました。
次にAPIキーを設定しリクエストします。

```console:console
$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'x-api-key: ****************************' https://****************************.amazonaws.com/dev -d '{"user_id":"20180118_02abcde","device_token":"token20180118_02"}' --dump-header -
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 4
Connection: keep-alive
Date: Thu, 18 Jan 2018 05:23:02 GMT
x-amzn-RequestId: ****************************
X-Amzn-Trace-Id: ****************************
X-Cache: Miss from cloudfront
Via: 1.1 ****************************.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ****************************
```

おっしゃあああ！できた！

# ハマった問題
## うまく引数が渡せない問題
ここでうまく引数を渡せず変だなーとなっている場合、メソッドがANYになっていたらPOSTやGETに変更するとうまくいきます。
私はそれで解決しました。

## 設定が反映されない問題
APIキー不要の設定にしたにも関わらずForbiddenが出続けるなど
デプロイ忘れてないか確認しましょう

## それでも解決しない時
APIを作り直しましょう！ｗ

# 追記、APIに独自ドメインを設定する方法
## ドメイン設定、SSL証明書発行
こちらを参考に証明書の発行まで進めてください。
https://qiita.com/miutex/items/1f515bc21111a5cf3fdb

## API Gatewayで設定
「カスタムドメイン名の作成」でエンドポイント設定をし、先程取得したACM証明書をセットします。
あとはベースパスマッピングでデプロイしたものを選択すればOKです。

# 参考
https://qiita.com/kojiro_ueda/items/303f2466e11b55e5ec21
https://qiita.com/tentatsu/items/c45bcc4062f1a6d4cf2a
https://qiita.com/Yuki_BB3/items/83198b4d9daca7ccd746
https://www.lanches.co.jp/blog/6776