API Gateway + Lambda + DynamoDB でサーバーレスのAPIを作ってみた

# はじめに

勉強としてデータを登録・検索・更新・削除できるAPIを作りました。

# 設計

<img width="583" alt="スクリーンショット 2019-01-14 13.24.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/184849/62aafd27-e700-7d6f-f4de-224bc806db96.png">

#  Output

- 全件取得（順不同）

```
$ curl -X GET -i "https://hogehoge.fuga-api.ap-northeast-1.amazonaws.com/prod/pokemons/2"

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
〜〜〜
{"pokemons":[{"number":7,"name":"ゼニガメ"},{"number":115,"name":"ガルーラ"},{"number":117,"name":"シードラ"}・・・]}
```
- 1件取得

```
$ curl -X GET -i "https://hogehoge.fuga-api.ap-northeast-1.amazonaws.com/prod/pokemons"

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
〜〜〜
{"number":2,"name":"フシギソウ","type":["くさ"," どく"]}
```

- 1件登録 → number、name、type を設定すると、1件登録できます。
    - 1つでも未設定の場合はメッセージを返します。
    - 同じ number のデータが存在する場合は登録せずにメッセージを返します。

```
$ curl -X PUT -i "https://hogehoge.fuga-api.ap-northeast-1.amazonaws.com/prod/pokemons" -H "Content-Type: application/json" -d "{ \"number\": 152, \"name\": \"チコリータ\", \"type\": [ \"くさ\" ] }"

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
〜〜〜
{"result":"Success"}

# name 未指定の場合
{"result":"Fail","reason":"nameが指定されていません。"}

# number がすでに登録されている場合
{"result":"Fail","reason":"ポケモンナンバー「1」のポケモンは既に登録されています。"}
```

- 1件更新
    - 1つでも未設定の場合はメッセージを返します。
    - 指定した number が存在しない場合はメッセージを返します。

```
$ curl -X POST -i "https://hogehoge.fuga-api.ap-northeast-1.amazonaws.com/prod/pokemons/152" -H "Content-Type: application/json" -d "{ \"name\": \"ベイリーフ\", \"type\": [ \"しんりょく\", \"リーフガード\" ] }"

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
〜〜〜
{"result":"Success"}

# 更新しようとしたデータが存在しない場合
{"result":"Fail","reason":"ポケモンナンバー「153」のポケモンは登録されていません。"}
```

- 削除
    - 1つでも未設定の場合はメッセージを返します。
    - 指定した number が存在しない場合はメッセージを返します。

```
curl -X DELETE -i "https://hogehoge.fuga-api.ap-northeast-1.amazonaws.com/prod/pokemons/152"

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
〜〜〜
{"result":"Success"}
```
## 一瞬つまづいたポイント

### API gateway から Lambda に値を引き渡すところ。

今回、URL の Path に含める情報と、リクエストbody に含める情報の2パターンで値を渡していましたが、ちょっと設定が特殊でした。
URL の Path に含める情報: number
リクエストbody に含める情報: name, type

- API gateway側の設定

リソース名を「{number}」にすると、Pathに値を設定することができます。
[対象のメソッド] > 総合リクエスト > マッピングテンプレート から以下の情報を入力して保存します。
  ● リクエスト本文のパススルー →「テンプレートが定義されていない場合 (推奨)」
  ● Content-Type → application/json

```
{
    "httpMethod": "$context.httpMethod",
    "number": "$input.params('number')",
    "name": $input.json("$.name"),
    "type": $input.json("$.type")
}
```
<img width="994" alt="スクリーンショット 2019-01-14 14.24.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/184849/547696f4-d04d-b213-1697-de73f28904f6.png">


- Lambda 側の実装

以下は、Node.js 6.10 で書いています。

```js

exports.handler = (event, context, callback) => {
    console.log(event.httpMethod); // これで「'POST'」と出力される
    console.log(event['number']); // Lambda 側では、Pathでもbodyでも同じ取得方法
    console.log(event['name']);
    console.log(event['type']);
}
```



## もやっとポイント

- 特定のカラムだけ取得することってできないのかな。。。。
   - 今回、「number と name の値だけ取得したい」というのができなくて、全件取得してからコード上でごにょごにょすることになってしまいました。DynamoDBは複雑なクエリを書けないというのは知っていたけど、それはJOINなどのレベルかと思っていたのでもやっと。（ドキュメント読み抜かしちゃってるのかな・・・）

- DynamoDBから値を取得すると、オブジェクトの中身が反転してキモチワルい。
   - カラム順 number name type なのに、name number type で返ってくる。アルファベット順？並べ替えるコード書いたけど、指定できるのかな。

## 今後の課題

- Lambdaのコードをもっとリファクタリングする。
	- ファイルの分け方がわからず、functionに切り出すのが面倒でまとめて処理を書いてしまったので汚い・・・・。

- セキュリティを強化する。
	- アプリとかアクセスを限定するときはCognitoの設定でセキュリティを強化できるそうですが、そこまで制約ない場合、どのようなセキュリティを組むべきなのかあまり理解できていません。AWS WAF を設定して最低限の攻撃を防ぐようにできるようですね。このあたりのベストプラクティスを勉強していきたいと思います。

- Swagger用にyamlファイルをExportしてAPIのテストしてみたい。

### 今回書いた Lambda のコード
とりあえずソースコードあげました！ 来月中にはREADMEとか詳細書いて、随時アップデートしていきます；； 
https://github.com/zhizit/lambda-sample-api



勉強中なので、おすすめのサイトとかあれば教えていただけるととても嬉しいです！
最後まで読んでくださりありがとうございましたm(__)m