🌏

【AWS】簡単なサーバーレスアプリケーションを構築する

2021/02/14に公開

はじめに

一通り自分でWebアプリケーションを作ってみたいなーと思ったのだが、サーバー立ててアプリケーションをデプロイしてっていうのが面倒だったのもあり、AWSを使ってサーバーレスで構築してみることにした。

本記事は、

  • サーバーレスアプリケーションを作ってみたいけど作ったことない
  • どのように作るかわからない
  • Lambdaとか使って何かAPIでも作ってみたい
  • S3の静的Web Hostingで何か作ってみたい

といったような方向けの記事となります。

作成するアプリケーション

今回は簡単な家計簿のアプリケーションを作ってみる。

今回作成する家計簿アプリ

  • トップ画面で「入力」もしくは「一覧」画面のどちらかに遷移できる。
  • 入力画面では、利用した日付、金額、メモを入力できる。
  • 一覧画面では、上記で入力した内容を参照できる。

構成


今回作成を目指す構成

利用サービスは以下の通り。

サービス 説明
S3 オブジェクトストレージサービス
S3の静的Web Hosting機能を使って、Webサービスを公開する。
Route 53 DNSサービス
S3で公開したサービスに対して、ドメインを付与する。
DynamoDB NoSQLデータベースサービス
家計簿のデータを保存する。
Lambda サーバーレスでコードを実行するサービス
入力した値を受け取り、DynamoDBにputする。
もしくは、DynamoDBからデータを参照する。
API Gateway LambdaをAPIとして実行するためのサービス
上記Lamdaで作成した関数をAPI化し、S3に保存したhtmlからajaxで実行する。

手順

以下の手順で作成する。

  1. S3にコンテンツ配置&静的Web Hostingの設定
  2. Route 53でドメイン設定
  3. DynamoDBテーブル作成
  4. Lambda関数作成(API Gateway含む)
  5. 入力フォーム&一覧表示ページ作成

1. S3にコンテンツ配置&静的Web Hosting設定

S3にコンテンツ配置

  • サービス > S3 > バケットの作成 を押下
  • 以下の通りバケットを設定する。
項目 設定内容
バケット名 cloud-demo.ga
※これは、この後設定するドメイン名と同じでなくてはならない。
パブリックアクセスをブロックする チェックを外す。
今回は、静的Web Hostingでページを全世界に公開するため、アクセスはブロックしない。
確認項目 チェックする。
  • ファイルをアップロードする。

  • cloud-demo.gaを選択し、「アップロード」を押下

  • ファイル、フォルダを選択し、「アップロード」を押下

  • 今回アップロードするファイルは以下。

cloud-demo.ga
├── index.html
├── css  
│   └── bootstrap.min.css
└── js
    └── jquery-3.5.1.min.js

https://www.microstone.info/spring-boot-2実践入門:簡単なwebアプリを一から作成チュー/

  • トップ画面のhtmlは以下
    ここで、input.htmlとlist.htmlへの参照があるが、これは後程追加する。
index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="css/bootstrap.min.css" />
    <script src="js/jquery-3.5.1.min.js"></script>
    <title>トップ</title>
</head>

<body>
    <nav class="navbar navbar-inverse">
        <div class="container">
            <div class="navbar-header">
                <p>トップ</p>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="card card-primary mb-3">
            <div class="card-header">
                <h5 class="card-title">トップ</h5>
            </div>
            <div class="card-body">
                <div class="form-group row">
                    <div class="col-md-10" align="center">
                        <button class="btn btn-primary" style="width:50%" onclick="location.href='./input.html'">入力</button>
                    </div>
                </div>
                <div class="form-group row">
                    <div class="col-md-10" align="center">
                        <button class="btn btn-primary" style="width:50%" onclick="location.href='./list.html'">一覧</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

  • 以下のようになる

静的Web Hosting設定

ここからはアップロードしたファイルがコンテンツとしてアクセスできるように設定を行う。

  • cloud-demo.gaバケットの「プロパティ」タブを選択
  • 一番下までスクロールすると、静的ウェブホスティングが無効になっていることがわかる。
  • 編集を押下し、以下の通り設定
項目 設定内容
静的ウェブサイトホスティング 有効にする
インデックスドキュメント index.html
  • 変更を反映すると以下のようになる。

  • エンドポイントが発行されていることがわかる。ここで実際にサイトにアクセスしてみる。エンドポイントを押下すると、以下の画面のようにAccess Denied画面に遷移する。

  • これはバケット自体の権限のアクセスが許可されていないためである。そこで「アクセス許可」タブからバケットポリシーの「編集する」を選択する。

  • 以下を張り付け「変更の保存」を押下する。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::cloud-demo.ga/*"
            ]
        }
    ]
}

ちなみにこれは以下の公式サイト参照
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/WebsiteAccessPermissionsReqd.html

  • 以下のように設定されたらOK

  • 再度先ほどのエンドポイントにアクセスすると、問題なくindex.htmlが表示されていることがわかる。

2. Route 53でドメイン設定

ドメイン取得

実際にRoute 53でドメインを設定する前に、予めドメインを取得する必要がある。今回は、以下のfreenomというサービスを使って、無料でドメインを取得する。

https://www.freenom.com/ja/index.html

  • 今回は「cloud-demo.ga」というドメインを取得した。
  • 実際にドメインを取得する手順については割愛させていただく。
  • ドメインを取得し、Name Serverの設定画面を表示しておく。

Route 53によるDNSの設定追加

ここでは、Route 53を使って、実際にS3の静的Web Hostingサイトに対して、cloud-demo.gaで名前解決を行えるように設定を追加していく。

  • サービス > Route 53 > ホストゾーンを選択

  • ホストゾーンの作成を押下。
    なお、ホストゾーンとは、ドメインの設定情報やルーティング情報をまとめた単位のこと。

  • 以下のように設定を追加する。
     
    ここでは、ドメイン名をcloud-demo.ga、タイプをパブリックとする。

  • すると、以下のようにデフォルトでNSレコードとSOAレコードが作成されていることがわかる。NSレコードは実際にcloud-demo.gaを管理しているサーバー情報が書かれているレコードであり、SOAレコードとはcloud-demo.gaの管理情報が記載されているレコードである。

  • 次に上記のNSレコードをfreenomのName Server設定画面のNameServer1~4にコピペして、「Change Nameservers」を押下。
    これは、cloud-demo.gaをAWSのRoute 53で管理するということをfreenomに教えてあげているというイメージである。

  • 最後に、S3のエンドポイントがcloud-demo.gaで名前解決できるように、Route 53でレコードを追加する。

  • Route 53 > レコードの作成を押下

  • 以下のように設定し「レコードを作成」を押下

項目 設定内容
レコード名
今回はサブドメインを追加しない。
レコードタイプ A-IPv4アドレスと一部のAWSリソースにトラフィックをルーティングします
エイリアス チェックする
すると、AWSのどのリソースにルーティングするかを選ぶことができるようになる。
リソース S3
リソース ap-northeast-1
バケット cloud-demo.gaのバケットを選択
ここでバケット名とドメイン名が同一でないと、候補に表示されないので注意
  • 以下の画面のようにレコードが追加される。

  • 時間が経つと(数分は待った気がする)、cloud-demo.gaでS3に配置したhtmlにアクセスできるようになる。

3. DynamoDBテーブル作成

ここからは、アプリケーションのバックエンドの作成を行っていく。
まず、家計簿情報を保存するデータベースの作成を行う。

ここからは以下の文献を参考にしている。

https://www.amazon.co.jp/dp/B0764C5MT5/

DynamoDBとは

  • フルマネージドサービス
  • 非VPC接続環境で利用できる。(VPCを意識しなくて良い)
  • Key-Value型のスキーマレスデータベース
  • 高い信頼性(裏ではMulti-AZ構成になっている)
  • トランザクション処理はカバーしていない。

ここでは、そんなにコストや性能は意識しないので、key-value形式でデータが保存できるんだなと思ったらよい。

  • ここでは以下のテーブルを作成する。

①家計簿テーブル

属性名 意味
id Number プライマリーキー, 連番となる
used_date String 出費があった日付
expense Number 出費額
memo String メモ, 用途などを書く
update_datetime String レコードの更新日

②sequenceテーブル

属性名 意味
tablename String プライマリーキー, idを採番するテーブル名
seq Number tablenameに記載のテーブルが保持している現在のid

家計簿テーブルはその名の通り、家計簿のデータを保持するテーブルである。

プライマリーキーはレコードを一意に表すIDであるが、DynamoDBには自動的に連番を採番する仕組みが存在しない。

そのため、連番を管理する別テーブルを用意しておき、読み込むたびにカウントアップしていく、という方法をとる。

それを行うために用意したのが上記②のsequenceテーブルであり、管理対象のテーブル名をtablenameで保持しておいて、それに対応するシーケンス番号(seq)を属性として保持するものである。


ここから実際にテーブルを作成していく。

  • サービス > DynamoDB > テーブル > テーブルの作成を押下
  • 以下の設定でaccountbookテーブルを作成する。
項目 設定内容
テーブル名 accountbook
プライマリーキー id
数値
  • 続いて、以下の設定でsequenceテーブルを作成する。
項目 設定内容
テーブル名 sequence
プライマリーキー tablename
文字列
  • 以下のように2つのテーブルが作成されたことがわかる。

  • 次に、連番の初期値を決めるために、sequenceテーブルに項目を追加する。

  • テーブル一覧画面から、「sequence」テーブルを選択し、「項目」タブから「項目の作成」を押下。

  • ①tablenameの項目にテーブル名「accountbook」を入力し、②̟̟+ボタンから「Append」を選択し、項目を追加する。③項目名を「seq」とし、型をNumber、その値として初期値0を入力する。その後「保存」を押下。

  • 以下のように設定が追加されたことが確認できる。

これでデータベースの作成は完了した。

4. Lambda関数作成(API Gateway含む)

ここから、作成したaccountbookテーブルにレコードを作成したり、値を参照したりするLambda関数の作成とそれをRestAPIとして呼び出せるようにAPI Gatewayの設定を行う。

AWS Lambdaとは

  • サーバーレスアーキテクチャと呼ばれ、サーバーの管理等を必要とせず、コードをアップロードするだけで、そのコードを実行できるフルマネージドサービスである。
  • Java、Go、PowerShell、Node.js、C#、Python、Rubyが実行できる。
  • Lambdaはイベントドリブンで実行され、S3へのバケットのファイルの読み書き、CloudWatchs Events, SNSトピックなど様々なイベントソースから実行できる。ここではAPI Gatewayをイベントソースとして、RestAPIとして実行できるようにする。


    今回は画面から入力された値をDynamoDBに登録するRegistAccountBook関数と、DynamoDBに保存されたデータを参照するGetAccountBook関数を作成する。

RegistAccountBookの作成

  • サービス > Lambda > 関数 > 関数の作成を押下する。

  • 関数の作成画面では「設計図の使用」から、「micorservice-http-endpoint-python」を選択し、「設定」を押下

  • 以下の設定で作成する。

項目 設定内容
関数名 RegistAccountBook
実行ロール AWSポリシーテンプレートから新しいロールを作成
ここでは、LambdaがDynamoDBにアクセスするための権限を作成する必要がある。
ロール名 LambdaAccessDynamoDBRole(何でも良いが)
ポリシーテンプレートオプション シンプルなマイクロサービスのアクセス権限(DynamoDB)
API APIを作成する
API REST API
最近はRest形式のAPIが多いイメージ
セキュリティ オープン
本当はダメ。
  • 以下のように、API GatewayがトリガーとなっているLambda関数が作成されていることがわかる。

  • また、IAMの画面に行くと、以下のようにLambdaAccessDynamoDBRoleが作成されていることがわかる。

  • 以下のように、CloudWatchにログを書き込む権限や、DynamoDBにアクセスするポリシーが存在することがわかる。

"logs:CreateLogStream",
"logs:PutLogEvents"
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Scan",
"dynamodb:UpdateItem"
  • 作成したLambda関数のコード欄に以下を設定し、「Deploy」を押下。
RegistAccountBook.py
import json
import boto3
import time
import decimal
import datetime

# DynamoDBオブジェクト
dynamodb = boto3.resource('dynamodb')

# 連番を裁判して返す関数
def get_next_seq(table, tablename):
	response = table.update_item(
		Key = {
			'tablename' : tablename
		},
		UpdateExpression='set seq = seq + :val',
		ExpressionAttributeValues = {
			':val' : 1
		},
		ReturnValues='UPDATED_NEW'
	)
	return response['Attributes']['seq']


def lambda_handler(event, context):
	try:
		# シーケンスデータを得る
		seqtable = dynamodb.Table('sequence')
		nextseq = get_next_seq(seqtable, 'accountbook')

		## フォームに入力されたデータを得る
		param = json.loads(event['body'])
		date = param['date']
		expense = param['expenses']
		memo = param['memo']

		# 現在の時刻を取得
		nowtime = time.time()
		timestamp = datetime.datetime.fromtimestamp(nowtime)

		# accountbook テーブルに登録する。
		table = dynamodb.Table('accountbook')
		table.put_item(
			Item = {
				'id' : nextseq,
				'used_date' : date,
				'expense' : decimal.Decimal(expense),
				'memo' : memo,
				'update_datetime' : str(timestamp)
			}
		)
		# 結果を返す
		return {
			    "isBase64Encoded": False,
			    "statusCode": 200,
			    "headers": {
			        'Content-Type': 'application/json',
			        'Access-Control-Allow-Headers': 'Content-Type',
			        'Access-Control-Allow-Methods': 'POST,GET',
			        'Access-Control-Allow-Origin': '*'
    			},
			    "body": json.dumps({"result":"ok"})
		}

	except:
		import traceback
		traceback.print_exc()
		return {
			'statusCode' : 500
		}





コードの詳細を記載する。

  • 以下のようにboto3を使って、DynamoDB.Clientオブジェクトを生成する。
# DynamoDBオブジェクト
dynamodb = boto3.resource('dynamodb')

  • 以下で、連番を採番して採番した結果を返すget_next_seq()関数を作成する。
# 連番を採番して返す関数
def get_next_seq(table, tablename):
	response = table.update_item(
		Key = {
			'tablename' : tablename
		},
		UpdateExpression='set seq = seq + :val',
		ExpressionAttributeValues = {
			':val' : 1
		},
		ReturnValues='UPDATED_NEW'
	)
	return response['Attributes']['seq']
  • DynamoDB.Tableオブジェクトと、プライマリーキーであるtabelnameを引数として受け取る。
  • DynamoDB.Tableオブジェクトのupdate_itemメソッドで値を更新する。
  • update_itemメソッドの引数は以下。
引数 説明
Key プライマリキーを入力する。ここでっとい引数のtablenameを渡す(実際には"accountbook"という文字列が入ることになる)。
UpdateExpression 更新する式。seq属性にvalパラメータの値をセットするという意味
ExpressionAttributeValues パラメータの値。:valが1であることを表す。パラメータの名称は:で始まる。
ReturnValues 更新した値を返す、を表す。

ReturnValuesに指定できる文字列などは公式ドキュメントを参照。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html


  • 以下で採番したidを取得する。
    sequenceテーブルを表す、DynamoDB.Tableオブジェクトを生成し、それを先ほど作成したget_next_seq()関数の引数とする。
# シーケンスデータを得る
seqtable = dynamodb.Table('sequence')
nextseq = get_next_seq(seqtable, 'accountbook')

  • 以下で実際に入力されたデータを取得する。
  • lambda関数への入力はevent引数にdictで渡されてくるので、そのbodyの値を参照する。bodyはJSON文字列形式で入力されてくる(実際にはこの後の画面作成でJSON文字列でインプットするようにする)ので、json.loads()関数でjsonに変換してあげる。
## フォームに入力されたデータを得る
param = json.loads(event['body'])
date = param['date']
expense = param['expenses']
memo = param['memo']

  • Pythonの組み込み関数time()で現在時刻を取得する。
  • Linuxタイムで取得されるので、frontimestamp()関数でタイムスタンプに変換する。
# 現在の時刻を取得
nowtime = time.time()
timestamp = datetime.datetime.fromtimestamp(nowtime)

  • 以下で実際にaccountbookテーブルに値を登録する。
  • DynamoDB.Tableオブジェクトを生成し、put_item()メソッドで登録する。
  • プライマリキーのidは上記で取得した値を登録する。
  • used_date, memo項目は、入力フォームから受け取ったdate, memoをそのまま登録する。
  • expenseは入力フォームから受け取った値は文字列になっているため、Decimalに変換する(DynamoDBにはfloat型がないとのこと)。
  • update_datetimeは上記で取得したタイムスタンプを文字列に変換して登録する。
# accountbook テーブルに登録する。
table = dynamodb.Table('accountbook')
table.put_item(
	Item = {
		'id' : nextseq,
		'used_date' : date,
		'expense' : decimal.Decimal(expense),
		'memo' : memo,
		'update_datetime' : str(timestamp)
	}
)

  • 以下で登録した結果を返す。
# 結果を返す
return {
	"isBase64Encoded": False,
	"statusCode": 200,
	"headers": {
		'Content-Type': 'application/json',
		'Access-Control-Allow-Headers': 'Content-Type',
		'Access-Control-Allow-Methods': 'POST,GET',
		'Access-Control-Allow-Origin': '*'
		},
	"body": json.dumps({"result":"ok"})
}
  • 実はここでハマった。
  • 以下に簡潔にTipsがまとまっていた。

https://qiita.com/maaz118/items/e20b64f088fbead07206

RegistAccountBookの動作確認

  • サービス > API Gateway > リソースに遷移
  • API Gatewayのリリースが作成されていることがわかる。

  • 「RegistAccountBook」を押下し、「Any」を選択すると以下のようになる。

  • テストしてみる。

  • テストの部分を押下すると以下の画面になるので、テストデータを入力し、「テスト」を押下。

項目 設定内容
メソッド POST
ヘッダー Accept:application/json
今回はjsonでPOSTする。
リクエスト本文 {"date":"2021-04-01","expenses":100,"memo":"test"}
  • 右側に結果が表示される。

  • DynamoDBを見に行くと、以下のようにレコードが登録されていることがわかる。

ここまでで家計簿登録のAPIが完成した。

GetAccountBookの作成

今度は値を取得するGetAccountBook関数を作成する。

  • RegistAccountBookと設定は同じ

  • サービス > Lambda > 関数 > 関数の作成を押下する。

  • 以下の設定で作成する。

項目 設定内容
関数名 GetAccountBook
実行ロール 既存のロールを使用する。
既存のロール LambdaAccessDynamoDBRole(先ほど作ったロール)
ポリシーテンプレートオプション シンプルなマイクロサービスのアクセス権限(DynamoDB)
API APIを作成する
API REST API
最近はRest形式のAPIが多いイメージ
セキュリティ オープン
さっきと同じように本当はダメ。
  • コードは以下。
GetAccountBook.py
import json
import boto3
import decimal

# DynamoDBオブジェクト
dynamodb = boto3.resource('dynamodb')

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
       if isinstance(obj, decimal.Decimal):
           return int(obj)
       return json.JSONEncoder.default(self, obj)

def lambda_handler(event, context):
	try:
		# accountbook テーブルから全データを参照する。
		table = dynamodb.Table('accountbook')
		response = table.scan()

		# 結果を返す
		return {
			    "isBase64Encoded": False,
			    "statusCode": 200,
			    "headers": {
			        'Content-Type': 'application/json',
			        'Access-Control-Allow-Headers': 'Content-Type',
			        'Access-Control-Allow-Methods': 'POST,GET',
			        'Access-Control-Allow-Origin': '*'
    			},
			    "body": json.dumps(response, cls=DecimalEncoder)
		}

	except:
		import traceback
		traceback.print_exc()
		return {
			'statusCode' : 500
		}

コードの詳細を記載する。

  • 実際に値を取得しているのは以下のコードのみ
  • DynamoDB.Tableのscan()メソッドで全データを取得できる。
# accountbook テーブルから全データを参照する。
table = dynamodb.Table('accountbook')
response = table.scan()

-上記で取得したデータを以下で返している。

  • 取得したデータをjson.dumps()でJSON文字列に変換している。
  • clsでエンコーダを指定。詳細は次
"body": json.dumps(response, cls=DecimalEncoder)
  • 以下でエンコードクラスを作成している。
  • Decimalをjsonで返そうとすると怒られるので、decimal型があればintにencodeして返しましょうね、ということ。
class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
       if isinstance(obj, decimal.Decimal):
           return int(obj)
       return json.JSONEncoder.default(self, obj)

詳細は以下がわかりやすい。
https://qiita.com/podhmo/items/dc748a9d40026c28556d

GetAccountBookの動作確認

RegistAccountBookの時と同様に、API Gatewayでテストしてみる。

  • 以下のように設定してテストを実行すると、DynamoDBに登録されている値が帰ってきていることがわかる。今回は値を取得したいのでGETメソッドを使う。

エンドポイントの確認

  • Lamdaの画面から、トリガーとなっているAPI Gatewayを選択すると、以下のようにAPIのエンドポイントが確認できるので控えておく。

5. 入力フォーム&一覧表示ページ作成

最後に、家計簿の入力フォームと一覧画面の作成を行う。
この画面で上記で作成したAPIをajaxで実行する。

  • 入力フォームをinput.html、一覧表示画面をlist.htmlとして作成する。

  • 入力フォーム
input.hml
<!DOCTYPE html>
<html lang='ja'>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="css/bootstrap.min.css" />
    <script src="js/jquery-3.5.1.min.js"></script>
    <title>サンプルフォーム</title>
    <script type="text/javascript">
    $(function() {
        $('#btnSend').on('click', function() {
            var apiurl = "RegistAccountBookのエンドポイント";
            var data = {
                'date': $('#date').val(),
                'expenses': $('#expenses').val(),
                'memo': $('#memo').val()
            }

            $.ajax({
                    url: apiurl,
                    type: 'POST',
                    dataType: 'json',
                    data: JSON.stringify(data)
                })

                .done(function(response) {
                    alert('成功です');
                })
                .fail(function(jqXHR, textStatus, errorThrown) {
                    alert('エラーです');
                })

        });
    });
    </script>
</head>

<body>
    <nav class="navbar navbar-inverse">
        <div class="container">
            <div class="navbar-header">
                <p>新規入力</p>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="card card-primary mb-3">
            <div class="card-header">
                <h5 class="card-title">サンプルフォーム</h5>
            </div>
            <div class="card-body">
                <div class="form-group row">
                    <label class="col-md-2 control-label">日付</label>
                    <div class="col-md-10">
                        <input class="form-control" type="date" id="date" />
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-2 control-label">金額</label>
                    <div class="col-md-10">
                        <input class="form-control" type="text" id="expenses" />
                    </div>
                </div>
                <div class="form-group row">
                    <label class="col-md-2 control-label">メモ</label>
                    <div class="col-md-10">
                        <input class="form-control" type="text" id="memo" />
                    </div>
                </div>
                <div class="form-group row">
                    <div class="offset-md-2 col-md-10">
                        <button class="btn btn-primary" id='btnSend'>送信</button>
                    </div>
                </div>
                <div class="form-group row">
                    <div class="offset-md-2 col-md-10">
                        <button class="btn btn-secondary" onclick="location.href='./index.html'">戻る</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>
  • 送信ボタンはid='btnSend'としておく。ボタンが押下されたときにAPIを叩くという処理を行うようにする。
<button class="btn btn-primary" id='btnSend'>送信</button>
  • 実際にAPIを実行するのは以下のjavascriptである。
  • 以下で、「btnSendのidを持つパーツがクリックされたとき」に処理を実行することを意味している。
$('#btnSend').on('click', function() {..処理...}
  • 以下でテキストボックスから入力された値を取得している。
var data = {
	'date': $('#date').val(),
        'expenses': $('#expenses').val(),
	'memo': $('#memo').val()
}
  • 以下で実際にajaxでAPIを実行している。
  • url属性にRegistAccountBookのエンドポイントを指定
  • typeはPOSTを指定
  • json形式でデータを渡すので、dataTypeにjsonを指定
  • dataには上記で作成した、dataオブジェクトをjson文字列に変換したものを渡す。
    変換せずに渡すと、Lambda側でjsonと解釈できなくなりエラーとなるの注意
 $.ajax({
	 url: apiurl,
         type: 'POST',
         dataType: 'json',
         data: JSON.stringify(data)
})
  • ajaxは非同期通信なので、$.ajax().done(), $.ajax().fail()を使ってAPIの実行結果を判定する。デバッグするときに使ったりするので覚えておくとよい。
 .done(function(response) {
	 alert('成功です');
})
.fail(function(jqXHR, textStatus, errorThrown) {
	alert('エラーです');
})

  • 次に一覧表示画面を作成する。
  • 以下のようにlist.htmlを作成する。
list.html
<!DOCTYPE html>
<html lang='ja'>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="css/bootstrap.min.css" />
    <script src="js/jquery-3.5.1.min.js"></script>
    <title>サンプルフォーム</title>
    <script type="text/javascript">
    $(document).ready(function() {
        // ページ読み込み時に実行したい処理
        var apiurl = "GetAccountBookのエンドポイント";

        $.ajax({
                url: apiurl,
                type: 'GET',
                dataType: 'json',
            })
            // Ajaxリクエストが成功した時発動
            .done(function(response) {
                var item = response.Items;
                var table = document.getElementById('accountbooktable');
                for (let i = 0; i < item.length; i++) {
                    var tr = $('<tr></tr>').appendTo(table);
                    $('<td>' + item[i].id + '</td>').appendTo(tr);
                    $('<td>' + item[i].used_date + '</td>').appendTo(tr);
                    $('<td>' + item[i].expense + '</td>').appendTo(tr);
                    $('<td>' + item[i].memo + '</td>').appendTo(tr);
                }

            })
            .fail(function(jqXHR, textStatus, errorThrown) {
                alert('エラーです')
            })
    });
    </script>
</head>

<body>
    <nav class="navbar navbar-inverse">
        <div class="container">
            <div class="navbar-header">
                <p>一覧</p>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="card card-primary mb-3">
            <div class="card-header">
                <h5 class="card-title">サンプルフォーム</h5>
            </div>
            <table class="table table-striped" th:if="${items.size()}">
                <thead>
                    <tr>
                        <th style="width: 20%">ID</th>
                        <th style="width: 20%">日付</th>
                        <th style="width: 10%">出費</th>
                        <th style="width: 20%">メモ</th>
                    </tr>
                </thead>
                <tbody id='accountbooktable'>
                </tbody>
            </table>
        </div>
        <div class="form-group row">
            <div class="offset-md-2 col-md-10">
                <button class="btn btn-secondary" onclick="location.href='./index.html'">戻る</button>
            </div>
        </div>
    </div>
</body>

</html>
  • 入力フォームと同じように、以下で実際にajaxでデータを取得している。
  • url属性にGetAccountBookのエンドポイントを指定
  • typeはGETを指定
 $.ajax({
	 url: apiurl,
         type: 'GET',
         dataType: 'json',
})
  • 以下で取得した結果を表示
  • 以下のような構文で、取得結果responseを取得できる。
  • responseオブジェクトのItemsで、APIでGETした結果が取得できる。
  • accountbooktable属性で指定したhtmlのテーブル要素に対して、テーブルの要素をfor文で回して追加していく。詳細は割愛する。
.done(function(response) {
	var item = response.Items;
	var table = document.getElementById('accountbooktable');
        for (let i = 0; i < item.length; i++) 
		var tr = $('<tr></tr>').appendTo(table);
                $('<td>' + item[i].id + '</td>').appendTo(tr);
                $('<td>' + item[i].used_date + '</td>').appendTo(tr);
                $('<td>' + item[i].expense + '</td>').appendTo(tr);
                $('<td>' + item[i].memo + '</td>').appendTo(tr)
	}
})
  • 最後に作成したhtmlをS3のcloud-demo.gaバケットにアップロードして以下のファイル構成になれば完了となる。
cloud-demo.ga
├── index.html
├── list.html
├── input.html
├── css  
│   └── bootstrap.min.css
└── js
    └── jquery-3.5.1.min.js

動作確認

  • http://cloud-demo.ga/ にアクセス。

  • 「入力」を押して、入力フォームに遷移

  • 値を入力して「送信」を押下

  • 画面は切り替わらないので、「戻る」を押下して、一覧画面に遷移

  • 今度は「一覧」を押して、一覧画面に遷移

  • 先ほどの入力した値が表示されている!

最後に

今回は、S3の静的Web Hostingを使って簡単なサーバーレスアプリケーションを作成した。
サーバーレスであると以下のようなメリットもあるので是非とも活用していきたい。

  • サーバーの管理等が不要なので、運用でかなり楽になる。
    ※裏ではAutoScalingとかも行われているので、自分たちで可用性を意識しなくて済む。
  • サーバーを立てているわけではないのでリクエストがないときにはほとんど課金されない。
    ※DynamoDBやS3に保存してあるデータ、Route53の利用料金のみ。

最後に、

この記事はAWS初学者を導く体系的な動画学習サービス
「AWS CloudTech」の課題カリキュラムで作成しました。
https://aws-cloud-tech.com

ありがとうございました。

以上です。

Discussion