はじめに
LambdaとDynamoDB使ってAPI作ることになったんで、いろいろ試したメモ
詰まった点や、解決法など、参考になれば。(主に自分用のメモですが)
なおメソッドはPOSTでやっています。GETの記事が多かったので参考になれば。
この記事でやること
- DynamoDBでテーブル作成
- Lambda上でDynamoDBの値を取得(select)
- Lambda上でDynamoDBに値を挿入(insert)
- Lambda上でテスト
- API Gatewayの設定
- curlによるリクエストとレスポンス
DynamoDBでテーブル作成
テーブルを作ります。

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

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

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

この画面で画面左の+ボタンを押すとカラムを追加できるので、ここでカラムを追加します。
なおカラムは永続的なものはプライマリキーとソートキーのみなので逐一設定する必要があります。
Lambdaで関数を作成する
IAMでDynamoDBにアクセス可能なロールを作成
Lambdaで関数を作成する際にロールを求められるので先に作成します。

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

Lambdaを選択

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

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

「関数の作成」をクリック
どうでもいいですが、myZaifChartは以前Alexaやった時に作ったものです。

「一から作成」を選択し
ロールは先ほど作成したものがプルダウンに表示されるのでそれを選択します。
名前は好きなものを。
入力したら「関数を作成」ボタンを押します。
Lambda上でDynamoDBの値を取得
scan
全取得するにはscan()を使います。
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を使います。
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と似ていますがソートキーまで設定した場合にプライマリキーは重複して入力できますので、そういった場合には複数のレコードを取得することができます。
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に値を挿入
こんな感じで
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上でテスト
こんな感じでテスト設定し動作を確認します。

ここまで出来れば、Lambdaを使ってDynamoDBと連携するところまでは上手くいっています。
ここからAPI Gatewayの部分を設定していきます。
API Gatewayの設定
API Gatewayの設定はLambdaのコンソールだと見れる項目が少なすぎてさっぱりなので、
API Gatewayのコンソールから設定する方が良いです。
LambdaコンソールでAPI追加するとメソッドもANYになってしまい、これも混乱のもとです。
APIの作成

「APIの作成」をクリック

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

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

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

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

終わるとこんな画面になります。
curlでテスト
ここで一旦、curlでリクエストしてみましょう。
$ 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点セットで紐付けを行います。

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

名前は好きなものを
APIキーが作成されました。
使用量プランの設定
使用量プランと書いてありますが課金設定ではありません(課金を匂わす文言だとは思いますが)
左ペインの「使用料プラン」を選択し、表示された画面から「作成」ボタンをクリックします。

名前:好きなものを
スロットリングの有効化:オフ
クォータ:オフ
APIステージの関連付け

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

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

メソッド側でAPIキーの必要性をTrueにします。
**ここまで終わったらデプロイしましょう。**デプロイしていないせいで思った通り動かないということがよくあります。
curlでテスト
リクエストしてみましょう
$ 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キーを設定しリクエストします。
$ 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を作り直しましょう!w
追記、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