AWSAmplifyを使って簡単なウェブアプリを作る

はじめまして。今回初めてブログ記事を担当させていただきますM.Tです。
この記事では「AWS Amplifyを使って簡単なウェブアプリを作る」という主旨で実際にウェブアプリを作ってみたのでその過程をご紹介します。
目次
- どんなアプリ?
- 使用するAWSサービス
- 作業ステップ
- 実践
- まとめ
どんなアプリ?
今回は対話型の脱出ゲームを作っていきたいと思います。
画面上に表示された二つの選択肢を選んで行き、最終的に部屋から脱出するというゲームです。

使用するAWSサービス
・Amplify
ウェブアプリのバックエンドの構築、ホスティング、デプロイを担います。
・Lambda
ウェブページからの入力に応じてDynamoDBからデータを取得し、そのデータをウェブページに返す関数を作成します。
・Amazon API Gateway
ウェブページとLambda関数をつなげる役割を担います。
・DynamoDB
ウェブページ上に表示する脱出ゲームの選択肢の内容を保存します。
作業ステップ
- Amplifyでウェブページを作成する。
- Lambda関数を作成する。
- DynamoDBでテーブルを作成する。
- Lambda関数とDynamoDBをつなげる。
- API Gatewayを作成し、webページとLambdaをつなげる。
- webアプリを動かしてみる。
実践
ステップ1 Amplifyでウェブページを作成する。
まずはAWS Amplifyのコンソール画面を開きます。

左から[すべてのアプリ]をクリックして出てきた画面の[New app]プルダウンから[Create app backend]をクリックします。
[App name]を入力して(今回App nameは RoomEscapeGame とします)、[Confirm deployment]をクリックします。
すると画面が切り替わり、バックエンドの環境構築中画面が表示され、数分程度で構築が終了します。

その後表示された画面で[Frontend environments]タブを選択します。
今回はGitを使わずに直接ソースコードをアップするので[Deploy without Git provider]をチェックしてから[Connect branch]をクリックします。
表示された画面のEnvironment nameにdevと入力します。
次にアップロードするソースファイルを作成します。
まず空のテキストファイルに以下のコードをコピーして貼り付けてください。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome to the Room Escape Game!</title>
</head>
<body>
部屋から脱出せよ!
</body>
</html>
貼り付けたらそのファイルを index.html という名前で保存してZIP圧縮します。(注:保存する際、文字コードはUTF-8を選択してください。)
作成したZIPファイルコンソール画面からアップロードします。

ファイルがアップロード出来たら[Save and deploy]をクリックします。
画面上のバーが緑色になったらデプロイ完了です。

「Domain」の下に今デプロイしたウェブページのリンクが表示されるのでクリックし、 下図のように表示されれば成功です。

ステップ2 Lambda関数を作成する。
まずLambdaコンソール画面から[関数の作成]をクリックします。
(Amplifyでアプリ作成の際、関数が4つ自動で作成されています)

[一から作成]を選び、関数名を入力します。(今回関数名はescapegameFunction とします)。
ランタイムは[Python3.9]を選んでください。
画像のように入力したら[関数の作成]をクリックします。
出来上がった関数のlambda_function.pyに以下のコードを上書きで貼り付けます。
import json
import boto3
# 使用するDynamoDBテーブルを取得します
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('EscapeGameDB')
# ハンドラを定義します
def lambda_handler(event, context):
# DynamoDBのパーティションキーを指定する値をeventから受け取ります
ID = event['ID']
# イベントから受け取った値をもとにDynamoDBテーブルの項目を指定し、取得します
response = table.get_item(
Key={
'ID':ID
}
)
item = response['Item']
print(item['Situation'])
name = 'name'
# DynamoDBテーブルからから取得した項目をウェブページに返します
return {
'statusCode': 200,
'body': item
} }
ステップ3 DynamoDBでテーブルを作成する。
DynamoDBコンソール画面左から[テーブル]画面を開き、[テーブルの作成]をクリックします。
テーブル名はEscapeGameDBパーティションキーはIDとし、右タブの[文字列]を[数値]に変更します。
ソートキーは空欄とします。

[テーブルの作成]をクリックします。
左ナビゲーションから[項目]を選択し、EscapeGameDBを選択します。
[項目を作成]をクリックし、表示された画面右上の[フォーム]を[JSON]に切り替えます。
エディタに以下のテキストを上書きで貼り付けます。
{
"ID": {
"N": "1"
},
"Correctchoice": {
"N": "2"
},
"Situation": {
"S": "あなたは閉じられた部屋にいます。外に出るには鍵が必要です。どうしますか?"
},
"Result": {
"S": "窓の外は断崖絶壁だった。あなたは崖から落ちてしまった。"
},
"Choice1": {
"S": "窓から外へ出る。"
},
"Choice2": {
"S": "押入れを開ける。"
}
}
[項目の作成]をクリックします。
テーブルに項目が追加されているのを確認したら、同様の手順で、以下の4つの項目を一つずつ追加していきます。
{
"ID": {
"N": "2"
},
"Correctchoice": {
"N": "1"
},
"Situation": {
"S": "押入れの中には宇宙人がいた。どうしますか?"
},
"Result": {
"S": "宇宙人は懐から光線銃を取り出し、あなたは撃たれてしまった。"
},
"Choice1": {
"S": "会話を試みる。"
},
"Choice2": {
"S": "パンチをする"
}
}
{
"ID": {
"N": "3"
},
"Correctchoice": {
"N": "1"
},
"Situation": {
"S": "宇宙人に気に入られたようです。宇宙人は懐から鍵を出し渡してくれました。どうしますか?"
},
"Result": {
"S": "あなたは宇宙人と仲良く暮らしました。"
},
"Choice1": {
"S": "鍵を使って部屋を出る。"
},
"Choice2": {
"S": "部屋にとどまる"
}
}
{
"ID": {
"N": "4"
},
"Correctchoice": {
"N": "2"
},
"Situation": {
"S": "あなたは外に出ました。外には自転車が置いてあります、どうしますか?"
},
"Result": {
"S": "あなたが乗った自転車は空高く飛んで行き、宇宙へ飛び立ちました。あなたは地球へ帰ってくることはありませんでした。"
},
"Choice1": {
"S": "宇宙人をかごに乗せて自転車で帰る。"
},
"Choice2": {
"S": "歩いて帰る。"
}
}
{
"ID": {
"N": "5"
},
"Correctchoice": {
"N": "0"
},
"Situation": {
"S": "あなたは宇宙人に別れを告げ、無事帰宅することができました。"
},
"Result": {
"S": ""
},
"Choice1": {
"S": ""
},
"Choice2": {
"S": ""
}
}
テーブルに5つの項目があるのを確認します。
ステップ4 Lambda関数とDynamoDBをつなげる
まず作成したLambda関数escapegameFunctionの実行ロールに作成したDynamoDBテーブルEscapeGameDBへのアクセス権限を付与します。
Lambdaのコンソール画面からescapegameFunctionを選択し、[設定]タブから[アクセス権限]→ロール名のリンクをクリックします。
するとIAMコンソールが開くので、[インラインポリシーの追加]をクリックします。

ポリシーの作成画面でJSONタブを選択し、以下のテキストを上書きで貼り付けます。
その際、”YOUR-TABLE-ARN”に先ほど作成したDynamoDBテーブルEscapeGameDBのARNを貼り付けます。(DynamoDBコンソールでテーブルを選択すると表示されます)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": "YOUR-TABLE-ARN"
}
]
}
その後[ポリシーの確認]をクリックし、[名前]にescapegameFunctionDynamoDBPolicyと入力してポリシーの作成をクリックします。
ポリシーが追加されたら、Lambdaコンソールを開き、テストイベントを作成します。
[テスト]タブからテストイベントのエディタに以下のテキストを貼り付けます。
{
"ID": 1
}

その後[変更を保存]をクリックして正常に保存されたことを確認したら[テスト]をクリックします。

[詳細]をクリックし、図のような表示がされたら成功です。
ステップ5 API Gatewayを作成し、webページとLambdaをつなげる。
まずはウェブページを更新します。
ステップ1で作成したindex.htmlファイルに以下のコードを貼り付け、ZIP圧縮します。
コードにはJavaScriptとVue.jsが含まれています。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js">
</script>
<meta charset="UTF-8">
<title>部屋から脱出せよ!</title>
<!-- Add some CSS to change client UI -->
<style>
body {
background-color: #ffffff;
}
label, button {
color: #000000;
font-family: Arial, Helvetica, sans-serif;
font-size: 20px;
margin-left: 40px;
}
input {
color: #232F3E;
font-family: Arial, Helvetica, sans-serif;
font-size: 20px;
margin-left: 20px;
}
</style>
</head>
<body>
<p>部屋から脱出せよ!</p>
<div id="Situation1">
<p>{{Situation}}</p>
<p>選択肢1:{{Choice1}}</p>
<button type="button" onclick="selectChoice('1')">選択肢1を選ぶ</button>
<p>選択肢2:{{Choice2}}</p>
<button type="button" onclick="selectChoice('2')">選択肢2を選ぶ</button>
<p><button type="button" onclick="selectChoice('0')">リスタート</button></p>
</div>
<script>
//DynamoDBから受け取った情報を表示するためのVueインスタンスを生成します
var vm = new Vue({
el:'#Situation1',
data:{
ID:'0',
Situation:'',
Choice1:'',
Choice2:'',
Result:'',
CorrectChoice:''
}
})
// 選択肢1、選択肢2、リスタートをクリックした時の条件分岐
var selectChoice = (playerChoice)=>{
//リスタートを選択したらゲームスタート時の項目をDynamoDBから取得
if (playerChoice == '0') {
vm.ID = 1;
callAPI();
//正解の選択肢なら次の段階の項目をDynamoDBから取得
} else if ((vm.CorrectChoice != '0') && (playerChoice == vm.CorrectChoice)) {
vm.ID = vm.ID + 1;
callAPI();
//不正解なら結果を表示
} else if ((vm.CorrectChoice != '0') && (playerChoice != vm.CorrectChoice)) {
vm.ID = 0;
vm.Situation = vm.Result;
vm.Choice1 = '';
vm.Choice2 = '';
vm.Result = '';
vm.CorrectChoice = '0';
}
}
//APIの呼び出しを定義する
var callAPI = ()=>{
//idは現在の次の段階を代入
var id = vm.ID;
// ヘッダーオブジェクトの生成
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
//Lambda側に渡す値の設定
var raw = JSON.stringify({"ID":id});
// JSONオブジェクトの生成
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
// API経由でのLambdaからのレスポンスをVueインスタンスに代入する
fetch("APIGateway", requestOptions)
.then(response => response.text())
.then(result => {
vm.ID = JSON.parse(result).body['ID'];
vm.Situation = JSON.parse(result).body['Situation'];
vm.Choice1 = JSON.parse(result).body['Choice1'];
vm.Choice2 = JSON.parse(result).body['Choice2'];
vm.Result = JSON.parse(result).body['Result'];
vm.CorrectChoice = JSON.parse(result).body['Correctchoice'];
return 0;
})
.catch(error => console.log('error', error));
}
</script>
</body>
</html>
作成したZIPファイルをAmplifyのコンソール画面からアップロードします。
バーが緑色になったらDomainのリンクをクリックします。

図のように表示されていれば成功です。
これでwebページ側とAWS側の準備が整ったので、API Gatewayを作成し両者をつなぎます。
API Gatewayコンソール画面から、REST APIの[構築]をクリックします。
最初のAPIを作成するという表示画面でOKを押します。
作成画面で
プロトコルを選択する→REST
新しいAPIの作成→新しいAPI
名前と説明→API名→escape_game_API
と入力して、APIの作成をクリックします。
リソース画面からアクションドロップダウンリストから[メソッドの作成]を選択、そこからPOSTメソッドを追加します。

統合タイプ→Lambda
Lambda関数→escapegameFunction
と入力し、[保存]します。
Lambda 関数に権限を追加するという画面でOKを押します。
メソッドが作成されたのを確認したら、アクションドロップダウンリストからCORS の有効化を選択し、[CORSを有効にして既存のCORSヘッダーを置換]を選択し、 確認画面で、[はい、既存の値を置き換えます]を 選択します。
項目のすべてにチェックが入ったらメソッドをテストをします。
POSTメソッドを選択します。

テストマークをクリックします。
リクエスト本文に以下のテキストを貼り付けます。
{
"ID": 1
}
[テスト]を押します。

図のようなレスポンスがあれば成功です。
作成したAPIをデプロイします。
アクションドロップダウンリストで[Deploy API]を選択します。
[デプロイステージ] → [新しいステージ]
[ステージ名] →「dev」
上記のように設定したら[デプロイ] を選択します。
ステップ6 webアプリを動かしてみる。
再度index.htmlを更新します。
87行目の”APIGateway”に先ほど作成したAPIの呼び出しURLを貼り付けます。
ZIP圧縮してAmplifyコンソール画面からアップロードし、リンクをクリックします。
画面の見た目は変わっていませんが、[リスタート]をクリックすると脱出ゲームがスタートします。

選んだ選択肢によって状況が進んでいきます。

DynamoDBの項目を編集することで正解の選択肢や状況を編集することができます。
以上で「AWS Amplifyを使って簡単なウェブアプリを作る」の実践は終了です。
まとめ
Amplifyを使うことでwebサイト構築のハードルが下がったかなと思いました。いろいろなタイプのwebサイトを試したり、リリースするのに、Amplifyを使ってみるのはいかがでしょうか。
