AWS Lambdaを使ってサーバレスアプリを作成(CRUDのR)

記事を閲覧いただき、ありがとうございます。[中村](https://twitter.com/nakam_aws)です！！
2020年10月にAWSエンジニアとして転職したので、AWSの予習も兼ねてLambdaを使ったアプリ作成について書いきます。

###  AWS Lambdaとは？
ここでつらつら説明するより[公式動画](https://youtu.be/Hr6ZIvXSZpw)の方がわかりやすいという結論に至りました。まずはご覧ください。
<iframe width="560" height="315" src="https://www.youtube.com/embed/Hr6ZIvXSZpw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

##### 以下を使用して作成ます。

フロントエンド : [Vue.js](https://jp.vuejs.org/index.html)
AWS(インフラ) : [S3](https://aws.amazon.com/jp/s3/), [API Gateway](https://aws.amazon.com/jp/api-gateway/), [Lambda](https://aws.amazon.com/jp/lambda/), [DynamoDB](https://aws.amazon.com/jp/dynamodb/)

### 構成
構成はこんな感じです。
DBはDynamoDB、アプリケーションサーバの代わりにlambdaを使ってサーバレスにする。
lambdaプログラムをAPI Gatewayと連携し、APIとして呼べるようにする。
Webサーバのように使えるS3にフロントエンドのプログラムをデプロイする。
![スクリーンショット 2020-10-17 16.27.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/b614b3a7-37b9-4f6c-b92b-ca7b16674874.png)

### 画面
画面こんな感じです。　
めちゃシンプルですが、自分の尊敬する人/好きな有名人などの情報を記録するアプリを作ります。
(余談：gifって初めて使ったけど便利ですねぇ。)
![persons画面イメージ.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/14f389e7-4919-a1d6-f074-4b86f86fcbf7.gif)

### 手順
以下のような手順で作成します。
が、死ぬほど長くなってしまったので、本記事は①②までとしました！！
③〜⑤は、後ほど作成します！ｶﾐﾝｸﾞｽｰﾝ!


##### ①DynamoDBテーブルを作成する
1.1 テーブル作成
1.2 カラム作成
##### ②GETメソッド作成(CRUDのR)
Lambda関数作成
API Gateway作成
Vue.js作成
##### ③POSTメソッド作成(CRUDのC)
Lambda関数作成
API Gateway作成
Vue.js作成
##### ④Deleteメソッドの作成(CRUDのD)
Lambda関数作成
API Gateway作成
Vue.js作成
##### ⑤PUTメソッドの作成(CRUDのU)

### 前提条件
AWSアカウント作成ずみであること
(フルアクセス権限のIAMユーザを作ってそこから操作することをお勧めします。)

### ① DynamoDBテーブルを作成する

※ここからAWSマネジメントコンソール画面を操作していきますが、
今後、画面のレイアウト、表示項目などが変わる可能性があります！
本記事と、実際の画面が違った場合は、心で感じ取って、うまく進めてください！！

#### 1.1 テーブル作成
AWSコンソールへログインし、DynamoDBの画面から「テーブル作成」を開く
<img width="700" alt="DynamoDB Home" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/daee1bf9-9caa-170a-e81d-53198a4401da.png">

テーブル名(作ろうとしているアプリ名が良いですかね)とプライマリーキーを入力し、
「作成」を押下する
<img width="700" alt="Create Table" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/d5271c3d-144b-52ca-de35-978b15f445e1.png">

#### 1.2 カラム作成
画面左のメニューから「テーブル」をクリック
作成したテーブル名クリック
「項目」タブを開き
「項目の作成」をクリック
<img width="700" alt="Create Columns" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/943fb604-8cdb-9a6e-a7a6-2e5a5de45e72.png">

「＋」をクリック、「Append」をクリック、「String」を選択
<img width="400" alt="Create Columns 2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/f2d86486-4d7a-ac06-3241-6222815fe9b7.png">

アプリに必要なカラム名を追加する。
<img width="300" alt="add column name" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/ccad4d7f-39aa-6f5e-fc6e-5ea15f6b120c.png">

同様の操作で必要なカラムを全て作成する。
<img width="300" alt="add all columns" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/b0d20d04-ae4d-6613-5113-10c683e0f3fe.png">

それぞれのカラムに適当なデータを入力します。(ドラえもんはPerson？？ まぁいいやw)
<img width="300" alt="put data" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/81a6c774-4051-4ad5-6ff2-23e19b923c8d.png">

「保存」ボタンを押下します。
※データを入れてあげないと下のような赤文字のエラーが出ます。。。😂
<img width="700" alt="save" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/70105fe1-3c3f-b00b-be9d-761d4840f158.png">

これでDBは完成です。 ぅえーい！(・∀・)
<img width="600" alt="DB Table Done" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/73c19a07-21b3-9300-f15c-b537a8183bbd.png">

### ② GETメソッド作成(CRUDのR)
ここからCRUDごとに処理を作っていきます。
#### 2.1 Lambda関数作成
AWSコンソールからlambda画面へ行き、「関数の作成」をクリックする
<img width="700" alt="create lambda button" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/30640a91-eb0c-73d7-5799-b4a9d3f16085.png">

「一から作成」を選択(デフォルト)、
関数名を入力(今回はgetメソッドなので```-get```とする)
「Node.js」を選択(デフォルト)
「関数の作成」をクリックする
<img width="700" alt="creating lambda" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/6ff05397-8ed0-b56b-3579-ef330df4edf5.png">

このような画面に遷移すればOKです。
<img width="600" alt="lambda created" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/4a5a3d2a-a68d-4c67-a7a9-22423281bf97.png">

###### 2.1.1 アクセス権限設定(IAMロール)
作成したlambda関数は現時点でDynamoDBにアクセスできません。(上記の作成手順の時に「アクセス権限」がノータッチだった為)
なので、先にそちらの設定をやっちゃいます。

作成したlambda関数画面から「アクセス権限」タブを開き、ロール名をクリックします。
このIAMロールはlambda関数作成時に自動で作られています。
<img width="500" alt="lambda access setting" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/b8f5b081-cede-71da-7d9c-af95e20399c8.png">

IAMロール画面が開き、「ポリシーをアタッチします」をクリック
<img width="600" alt="attaching policy to role" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/dde0da9e-d91b-6b7c-8b7a-2fe5c228cc81.png">

検索窓に「dynamo」くらいで検査し、「AmazonDynamoDBFullAccess」を選択する
「ポリシーのアタッチ」をクリックする
<img width="600" alt="attaching policy to role" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/8488408e-b00e-6a82-5ce5-0275044f6f5d.png">

以下メッセージが出れば、ロール設定完了です。
<img width="500" alt="attaching policy to role" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/ef0e23a3-f41f-38d0-4d34-cc4e3f78a050.png">

###### 2.1.2 Lambda関数内にGet処理を書く

いよいよlambda関数にgetメソッドを書いていきます。

```index.js
const AWS = require('aws-sdk')
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = (event, context, callback) => {
    const httpMethod = event.httpMethod
    const params = {
        // ここで作成したDynamoDBテーブル名を指定
        'TableName': 'lambdaapp-persons'
    }
    dynamo.scan(params, function (err, data) {
        const response = {
            statusCode: 200,
            body: JSON.stringify(data.Items)
        }
        callback(null, response)
    })
};
```


コードエディタの部分にコードを貼り付け、「Deploy」をクリックする
<img width="600" alt="edit lambda code" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/8c6f40d8-7302-39d5-ca84-8391d81ef0c0.png">

###### 2.1.3 Lambda関数をテストする

「テストイベントの選択」プルダウンより「テストイベントの設定」を選択
<img width="600" alt="lambda test" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/5ae64a88-de02-9786-f724-92dffeaa6eaf.png">

任意のイベント名を入力し、「作成」をクリック
※Getメソッドではパラメータは特にないので、エディタの操作は不要
<img width="600" alt="skip test settings" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/1299c4f5-5839-c546-1dd3-7de04b70de17.png">

作成したテストイベントが選択された状態で「テスト」をクリック
<img width="600" alt="test" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/bb51d8d4-9633-1cb8-9359-53091e4cb659.png">

うまくいくと「実行結果：成功」と表示されます。
<img width="600" alt="test success" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/108a0094-feb6-ee19-b8f8-a84382caca3c.png">

結果の詳細を見てみると、DynamoDBに登録したデータが取れているのが分かります。
<img width="600" alt="test success" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/4bd64e2c-20fe-8724-9359-e8f779130601.png">



#### 2.2 API Gateway作成

###### 2.2.1 API Gateway作成

先ほど作成したGetメソッドをフロント側から呼び出せるようにAPI Gatewayを作成します。

API Gateway画面から「API 作成」をクリック
<img width="600" alt="create api" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/4e0581cf-fa58-289f-4f6c-9ac3449746e9.png">

API Typeを選択します。「REST API」の「構築」をクリック
<img width="400" alt="select api type" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/2f228edd-4f96-37cc-3282-a72ac681b751.png">

「新しいAPI」(デフォルト)を選択、
API名を入力(１つのAPIでCRUD全て追加するので、ここではget、postなどは入れていません)、
「APIの作成」をクリック
<img width="500" alt="api name etc" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/c75ef041-8381-38e3-4ff3-e34078161459.png">

###### 2.2.2 メソッドの作成

作成したAPI Gatewayの画面に遷移します。
「アクション」から「メソッドの作成」をクリック
<img width="500" alt="action create method" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/6ee33fea-c3e4-87c0-e5c4-39ddef540f09.png">

プルダウンから「GET」を選択し、チェックマークをクリック
<img width="300" alt="method type get" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/b7826d64-28f0-d1d6-0470-00f70e2faea8.png">

「GET - セットアップ」画面が現れます。
統合タイプ「lambda関数」を選択し、先ほど作成したlambda関数名を入力
「保存」をクリック
<img width="600" alt="lambda name and save" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/80309ad3-7b7e-4e50-fce4-9be79a6eabd1.png">

下記メッセージはOKでOK。 ぇ？w
<img width="400" alt="API to lambda trigger" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/27541fd0-6226-db9b-dfd3-e16e3884e604.png">

Lambda関数を見てみると、上記「権限を与える」操作によりAPI Gatewayがトリガーとして追加されます。
<img width="600" alt="lambda has trigger" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/5108ec75-683d-8af8-dc59-2261579a4def.png">

###### 2.2.3 メソッドをテスト
作成したAPI Gatewayメソッドからlambda関数が実行できるかテストします。

「テスト」をクリック
<img width="500" alt="api gateway test" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/ecb59f93-cfc4-2f52-81cb-dfa31a572680.png">

「テスト」をクリック！！
<img width="600" alt="api gateway test" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/be628a7a-d31f-f6f3-9820-e4e764ce2e3d.png">

ボタンの下にテスト結果が表示されます。
ステータス：200であればOK。　bodyの所にDynamoDB作成時に作ったデータが入っていますね。
<img width="400" alt="test result" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/f66e8734-09c4-2c3c-8dda-d7d1f48cb642.png">

###### 2.2.4 CORSの有効化
説明しよう！　CORSとは！
Cross-Origin Resource Sharing(オリジン間リソース共有)の略で
異なるオリジンとの通信ができるようにブラウザへ指示する仕組みだそうです。
今回の例で言うと、HTMLファイルをS3にアップロードして、S3から発行されたURLでブラウザに表示します。
さらにHTMLファイルの中にAPI GatewayのURLを書いて、メソッドを呼び出しています。
①S3のURLと②API GatewayのURLが異なるオリジンということになり、
それらを同時に扱うためにCORSが必要という事ですね。
[参考](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)

「アクション」から「CORSの有効化」をクリック
<img width="400" alt="enable cors" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/ec95ee0f-b89a-2b8e-eb4f-c84c87f95327.png">

GETメソッドに✅が入っていることを確認し、「CORSを有効にして既存のCORSヘッダーを置換」をクリック
<img width="600" alt="enable cors" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/86af60b4-7d1e-d036-4758-6c8a8cdee857.png">

「はい、既存の値を置き換えます」をクリック
<img width="500" alt="enable cors" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/eb89fa71-338c-4c14-16f9-54434894f5d4.png">


全ての項目に✅が付き、エラーが出なければOK
<img width="500" alt="enable cors" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/4b49ccda-5d1d-d910-ef68-bc24bf3f04f1.png">


###### 2.2.5 メソッドをデプロイ
「アクション」から「APIのデプロイ」をクリックします。
<img width="500" alt="api deploy" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/bca6af6a-83c7-cad2-bc98-179560577fb7.png">

デプロイされるステージはプルダウンから「新しいステージ」を選択
ステージ名に今回は「dev」と入力。※開発用だったらdev、商用だったらprodといった具合に分けるようですね
ステージの説明、デプロイメントの説明の入力は任意
「デプロイ」をクリック
<img width="400" alt="set stage" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/8b37e475-6892-5352-b782-ae7b409b32f4.png">

デプロイが成功すると、APIのURLが表示されます。先ほど入力したステージ名が末尾に入っていますね。
<img width="６00" alt="set stage" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/94870a52-731e-d083-c914-9d89559c4392.png">

#### 2.3 Vue.js作成

###### 2.3.1 ローカルでVue.jsを作成
まずはローカル環境にindex.htmlを作成し、vue.jsで書いていきます。
```[作成したAPI GatewayのURL]```の箇所を```Ctrl + F```で探して頂き、作成したAPIのURLを書きます。
ソースは[Github](https://github.com/nakamura0736/persons-cdn/blob/master/vue/1_get/index.html)でも参照いただけます。

```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Persons</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
  <div id="app">
    <div class="container mt-5">
      <div class="row">
        <div class="col-md-12">
          <h1>{{ title }}</h1>
          <h1>{{ psersons }}</h1>
          <p>Please add persons whom you like or respect, and describe them in detail.</p>
          <table class="table">
            <thead class="thead-dark">
              <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Description</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="person in persons">
                <td>{{ person.id }}</td>
                <td>{{ person.name }}</td>
                <td>{{ person.description }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    const vue = new Vue({
      el: "#app",
      data: {
        title: 'Persons',
        form: {
          id:'',
          name:'',
          description:''
        },
        persons: '',
        editIndex: -1,
        createFlag: true
      },
      mounted(){
        axios
          .get('[作成したAPI GatewayのURL]')
          .then(response => (this.persons = JSON.parse(response.data.body)))
          .catch(function(error){ 
            alert('Personsデータの取得に失敗しましたっ！\n(・Д・)ﾅﾝﾀﾞｯﾃｪ!!' );
            console.log(error);
          });
      }
    });
  </script>
</body>
</html>
```

HTMLファイルをブラウザで実行すると以下のような画面が表示されます。
(ドラえもんだけだと寂しいので、もう１レコード追加してみました。てかPersonじゃないっすねwww)
<img width="600" alt="ui" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/f784a709-b54c-87f6-1ec9-b2faf1e99635.png">

###### 2.3.2 作成したVue.jsをS3へアップロード

まずS3バケットを作成します。
S3画面へ飛び、「バケットを作成」をクリック
<img width="500" alt="create bucket" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/77a11f9e-5e4a-9d1a-b2fa-ede9efa28b98.png">

バケット名を入力します。下へスクロール。
<img width="500" alt="create name" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/4bff4465-93b4-1cd9-7e52-ba8d0ebb5c5a.png">

「パブリックアクセスを全てブロック」の✅を外し、下２つに✅をつける。
なかなか分かりづらいですが、要するにS3にあるHTMLファアイルをインターネットから参照できるようにしています。
さらに下へスクロール。
<img width="500" alt="S3 access control" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/9204d5c0-acba-ef42-19a9-516f0e8ad56d.png">

バケットのバージョニングは有効にすると、間違ってアップロードした時なんかに
元に戻せたりするみたいですね。お好みでどうぞ。
筆者はデフォルトの「無効」とします。
<img width="500" alt="versioning" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/8d7ac7d1-3df9-9485-a941-79224d57f98b.png">

さらに下へスクロールし、「バケットを作成」を押下する。
<img width="500" alt="create bucket" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/ad27f9ba-ed1d-865b-e2cc-e9466268e855.png">

正常に作成されました、と言うメッセージが表示されます。
作成したS3バケットをクリック
<img width="500" alt="create bucket success" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/772bf03f-9c1f-dba0-8ebb-06f27f3faff8.png">

下図のエリアにhtmlファイルをドラッグ&ドロップする
<img width="600" alt="html drug drop" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/870e8182-e321-a64e-f3c9-93316ddeedb4.png">

HTMLファイルが表示されます。下へスクロール
<img width="600" alt="html appeard" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/f6dd4058-f4a4-41d3-3b1c-4c0ff918b35d.png">

「アップロード」をクリック
<img width="600" alt="html appeard" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/93a55160-0d00-e911-3596-02143f633509.png">

アップロード成功しました。
<img width="500" alt="html appeard" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/0a70178f-ffc2-8fe0-6570-d97b3fb12258.png">

作成したバケットの画面に戻り、
アップロードしたHTMLファイルに✅をつけ、
「アクション」から「公開する」をクリック
<img width="600" alt="koukai suru" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/5c4d94b9-8783-0ff4-2197-04dfa220a0ba.png">

「公開する」をクリック
<img width="600" alt="koukai suru" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/4464fd98-19bd-4574-ebec-a70c7efd1167.png">


正常に公開されました。とメッセージが表示されますね。
<img width="600" alt="koukai suru" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/1ee9b593-4fa7-3f45-bcaf-bbef5f056a74.png">

再びバケット画面に戻り、HTMLファイルをクリック
<img width="500" alt="go to its url" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/29b215f2-50e6-d8a5-95f5-7e56ac963b22.png">

「オブジェクト URL」をコピー
<img width="600" alt="url" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/c78d583f-77a0-48f1-3859-a982f75eb71e.png">


URLをブラウザで実行して、動作確認
(余談ですが、Qiita執筆中にChromeのアプデが来ましたね。右上の「更新」ってやつ)
<img width="600" alt="url" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530421/16196440-2651-67e5-31cb-b0d3abcc2b7e.png">

### 終わりに

##### Qiitaを書いてみて
Qiitaを書くこと自体にめちゃめちゃ時間がかかってしまい、
Getメソッドのみになってしまいました！！
画面操作をここまで細かく説明する必要あるのか？という疑問も浮かびましたが、なるべく初学者目線で、をモットーに
そのまま描き続けました。
果たして分かりやすい記事になったのだろうか。。。！！

##### lambdaについて
今回はシンプルなアプリを作成しましたが、
きっともっと色んなことがlambdaを使ってできるはずなので、
(動画や画像など、様々なデータを扱ったりとか)
引き続き、何かを作りながら知見を深めていこうと思います！！