虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

API GatewayとLambdaでサーバーレスなWEBアプリを作ってみる

こんにちは、最近はお鍋が主食な虎の穴ラボのS.Sです。カット野菜を使うと手軽に調理が出来て便利ですよね。

さて今回は、虎の穴ラボ Advent Calendar 2020 - Qiita 9日目の記事になります。
8日目は礒部さんが機械学習について記事を書いているので、ぜひ読んでみてください。
10日目は山田さんの「コンテナイメージの脆弱性診断ツール Trivy」についての記事です。

以前から興味がありましたサーバーレスを学ぶために、Amazon API Gateway(以降、API Gateway)とAmazon Lambda(以降、Lambda)でサーバーレスの簡単なWEBアプリを作ってみたいと思います。

今回作るWEBアプリ(スタンプ)

f:id:toranoana-lab:20201206004447g:plain
こちらが押したスタンプが画面上部に反映されるのはもちろん、他のブラウザで押されたスタンプもリアルタイムに表示されます。

システム構成

f:id:toranoana-lab:20201206000413p:plain API Gatewayにて必要なAPIを作成します。APIの実処理はLambdaにて行います。
また押されたスタンプを記録するために、Amazon DynamoDB(以降、DynamoDB)を使用します。

DynamoDBでテーブル作成

まずは、DynamoDBでDBを作成します。
以下が、今回作成するテーブルの見本です。

テーブル項目

項目 名前
テーブル名 stamp
パーティションキー stamp_key 文字列
ソートキー row_id 数値
(追加項目) stamp_no 数値

stamp テーブル(見本)

stamp_key(パーティションキー) row_id stamp_no
maid_stamp 1 1
maid_stamp 2 3
maid_stamp 3 2

テーブルの作成は、以下の画面上でテーブル名プライマリキー(パーティションキー)を入力するだけで作成可能です。別途、ソートキーの指定も可能です。
今回は、テーブル名:stamp、パーティションキー:stamp_key(文字列)、ソートキー:row_id(数値)を指定しました。
DynamoDBでは、テーブルを作成する際にパーティションキーを設定しますが、これは検索の際にパーティションキーで取り出すデータの範囲を絞った検索が可能になります。
今回、パーティションキーは文字列(maid_stamp)を指定しました。

f:id:toranoana-lab:20201206014129p:plain

追加項目の追加は、テーブルの作成後に「項目の作成」から追加出来ます。
f:id:toranoana-lab:20201207200130p:plain


row_idの「+」を押して、「Append」にて項目を追加します。
追加する項目は、stamp_no(数値)になります。
またvalue値に値を入力すると、データの挿入も可能です。
f:id:toranoana-lab:20201207200441p:plain

「保存」を押すと、項目の追加と同時にデータも追加されました。 f:id:toranoana-lab:20201206015355p:plain


これでDBの準備は完了です!

LambdaでAPIのプログラムを作成

次に、APIのプログラム処理をLambdaで作成します。 今回は、テンプレートは「一から作成」で、ランタイムは「Node.js 12.x」にて作成します。 f:id:toranoana-lab:20201206015613p:plain

作成したLambda関数のindex.jsに以下のソースを実装します。 f:id:toranoana-lab:20201207212613p:plain

DynamoDBからスタンプを取得する関数

[index.js]

var AWS = require('aws-sdk');
var documentClient = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {
  var params = {
    TableName : 'stamp',
    KeyConditionExpression: 'stamp_key = :key',
    ExpressionAttributeValues: {
      ':key': 'maid_stamp'
    },
    ScanIndexForward: false,
    Limit: 10
  };
  var result = await documentClient.query(params).promise();

  var response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify(result)
  };
  return response;
};

AWS.DynamoDB.DocumentClient にて、LambdaでDynamoDBの操作が可能になります。
documentClient.query() で、DynamoDBにQuery検索が出来ます。 今回、Queryで指定したパラメータは以下になります。

KeyConditionExpression:パーティションキーのカラムを指定。
ExpressionAttributeValues:KeyConditionExpressionの値を指定。これにより、maid_stampに絞ったデータ抽出が可能になります。
ScanIndexForward:falseを指定するとソートキー(今回は、row_id)の降順でソートされた形でデータ抽出されます。trueは昇順になります。
Limit:最大数件を指定します。(今回は10件)

送信したスタンプをDynamoDBに記録する関数

[index.js]

var AWS = require('aws-sdk');
var documentClient = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {
  var event_body = JSON.parse(event.body);
  var put_stamp_no =  event_body.stamp_no;

  var get_params = {
    TableName : 'stamp',
    KeyConditionExpression: 'stamp_key = :key',
    ExpressionAttributeValues: {
      ':key': 'maid_stamp'
    },
    ScanIndexForward: false,
    Limit: 1
  };
  var result = await documentClient.query(get_params).promise();
  var new_id = result.Items[0].row_id;
  new_id++;

  var put_params = {
    TableName : 'stamp',
    Item:{
        stamp_key: 'maid_stamp',
        row_id: new_id,
        stamp_no: put_stamp_no,
    }
  };
  await documentClient.put(put_params).promise();
  var response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    body: 'OK!'
  };
  return response;
};

JSON.parse(event.body)にて、APIから送信されたのリクエストボディの値(押されたスタンプNo)が取得出来ます。
また、ソートキー(row_id)を設定するために、最新のレコードのrow_idを取得して、インクリメントしています。
上記のパラメータを使って、documentClient.put() にてDBに記録します。

API Gatewayの設定

次にAPI Gateway にて、スタンプを送信するためのAPIとスタンプを取得するための2つのAPIを作成します。
今回は、API タイプ「REST API」にて作成しました。 f:id:toranoana-lab:20201206020548p:plain

作成するAPI

用途 パス メソッド
DBからスタンプを取得 /stamp/ GET
送信したスタンプをDBに記録 /stamp/put/ POST

APIの作成

まずは「アクション」>「リソースの作成」で/stampを作成します。それに紐づける形で「アクション」>「メソッドの作成」で GETメソッドを作成します。これで取得用のAPIが出来ました。
次に、stampの下に「アクション」>「リソースの作成」で/putを作成します。それに紐づける形で「アクション」>「メソッドの作成」で POSTメソッドを作成します。これで送信用のAPIも出来ました。
f:id:toranoana-lab:20201208175932p:plain

APIとLambda関数の紐付け

作成しているAPIのセットアップで、統合タイプを「Lambda 関数」に指定することで、先ほど作成したLambda関数との紐付けが出来ます。 その際「Lambda プロキシ統合の使用」にチェックを入れておきます。 f:id:toranoana-lab:20201207213859p:plain

APIのデプロイ

APIをデプロイすることで、APIが外部公開され、外部からのアクセスが可能になります。 リソースを選択した上で「アクション」>「APIのデプロイ」を選択します。
今回ステージ名は、prodにしました。 f:id:toranoana-lab:20201207204351p:plain
これで、外部アクセスが可能なURLが発行されました。 f:id:toranoana-lab:20201208180205p:plain

フロント側のプログラムを作成

以下の2つのファイル(stamp.html、stamp.js)をローカル上で作成します。
[stamp.html]

<!DOCTYPE html>
<html>
  <head>
    <title>サーバーレスでスタンプ</title>
    <script type="text/javascript" src="stamp.js"></script>
    <style type="text/css">
      .stamp {
        cursor: pointer;
        margin-right: 20px;
      }
    </style>
  </head>
  <body>
    <div id="display_stamp" style="width: 800px;"></div>
    <div style="margin: 30px;">
      好きなスタンプを押してね!</br></br>
      <span class="stamp" onclick="put_stamp(1)"><img width="100" height="100" src="https://raw.githubusercontent.com/toranoana/special/master/maid-engineers/1_Introduction.png"/></span>
      <span class="stamp" onclick="put_stamp(2)"><img width="100" height="100" src="https://raw.githubusercontent.com/toranoana/special/master/maid-engineers/2_Introduction2.png"/></span>
      <sapn class="stamp" onclick="put_stamp(3)"><img width="100" height="100" src="https://raw.githubusercontent.com/toranoana/special/master/maid-engineers/3_PC(crying).png"/></sapn>
    </div>
  </body>
</html>

メイドちゃん画像は、toranoanaのGitHubの「とらラボ素材集」を使用しています。
こちらの画像は、商用問わず全て無料で使用できますので、プレゼンの資料作成などに、どうぞ活用してください。 github.com

[stamp.js]

window.onload = function(){
  setInterval(get_stamp,1000);
}

function get_stamp(){
  var req = new XMLHttpRequest();
  req.open(
    "GET",
    "https://*********.amazonaws.com/prod/stamp"
  );
  req.send();
  req.onreadystatechange = function () {
    var result = document.getElementById("result");
    var data = JSON.parse(req.responseText || "null");
    if(data){
      var displayStamp = document.getElementById('display_stamp');
      displayStamp.innerHTML = '';
      data.Items.forEach(item => {
         var addHtml;
         if(item.stamp_no == 1){
           addHtml = '<img width="80" height="80" src="https://raw.githubusercontent.com/toranoana/special/master/maid-engineers/1_Introduction.png"/>';
         }
         else if(item.stamp_no == 2){
           addHtml = '<img width="80" height="80" src="https://raw.githubusercontent.com/toranoana/special/master/maid-engineers/2_Introduction2.png"/>';
         }
         else if(item.stamp_no == 3){
           addHtml = '<img width="80" height="80" src="https://raw.githubusercontent.com/toranoana/special/master/maid-engineers/3_PC(crying).png"/>';
         }
         displayStamp.insertAdjacentHTML('afterbegin', addHtml);
       });
    };
  }
}

function put_stamp(stamp_no){
  var req = new XMLHttpRequest();
  req.open('POST','https://**************.amazonaws.com/prod/stamp/put');
  var json = JSON.stringify({
    stamp_no: stamp_no
  });
  req.setRequestHeader("Content-Type", "application/json");
  req.send(json);
  get_stamp();
}
get_stamp 関数

ページ読み込み時と、setInterval()で1秒毎に、スタンプ取得APIを叩いて、スタンプの取得と画面描画する関数です。

put_stamp 関数

スタンプが押された際に、スタンプ送信APIを叩いて、スタンプを記録する関数です。


stamp.htmlをブラウザで表示すれば、WEBスタンプが動作します! f:id:toranoana-lab:20201206011958p:plain

まとめ

今回初めてサーバーレスでWEBアプリを一通り組んでみましたが、API Gatewayの設定も簡単に出来て、Lambdaの組み合わせもUI上で簡単に行えることが出来ました。またDynamoDBと組み合わせることで、サーバーレスなWEBアプリの構築が出来ました。
Lambdaの使用には、関数に対するリクエストの数とコードの実行時間に基づいた料金が発生しますが、 Lambda には、100 万件の無料リクエスト、および 40 万 GB-秒の無料利用枠が1 か月ごとに設けられているので、個人の学習目的に使うには十分足りるかと思います。参考:料金 - AWS Lambda |AWS
ぜひ今回の記事を参考にお試しください。

P.S.

虎の穴ラボではいくつかのオンラインイベントを企画しております。是非ご参加ください!!

【オンライン】とらのあなラボエンジニア座談会Vol.5【リーダー対談】

12/18(金) 19:30から「虎の穴ラボ」社員によるトークイベントを準備しております。 yumenosora.connpass.com

TORA LAB Management & Leader Meetup

12/23(金) 19:30からとらのあなが運営している「とらのあな通販」と「Fantia」の開発の魅力を発表し、参加いただいた方の気になる点やご質問に答えるイベントとなっています。 yumenosora.connpass.com

その他採用情報

虎の穴ラボでの開発に少しでも興味を持っていただけた方は、採用説明会やカジュアル面談という場でもっと深くお話しすることもできます。ぜひお気軽に申し込みいただければ幸いです。 カジュアル面談では虎の穴ラボのエンジニアが、開発プロセスの内容であったり、「今期何見ました?」といったオタクトークから業務の話まで何でもお応えします。 カジュアル面談や採用情報はこちらをご確認ください。 yumenosora.co.jp