概要
2018年に結婚しました!ので!
ここはエンジニアらしくパーティ招待状Webページをつくってみることにしたのが始まりです。
とりあえず無事にパーティも終わったので、開発時の記憶をさかのぼりながら残す備忘録ですが
申請から開発まで一通り殴り書くので、何かしら参考になれば幸いです。┗(^o^)┛
…ちょっと前の記憶を掘り起こしながらやるので、間違って書いてそうなところもある気がしますが温かい目で御覧ください
開発環境はMacなので、Winの方は適宜読み替えていただけると幸いです
AWS上で日本語表示できている部分は、日本語の画面で説明してる…はずです
成果物
webページ
ざっくり3つの画面構成です。(3つ目はフリー素材やOSSの情報なので割愛)
GoogleMapや開催日を記載したホーム画面と、実際に参加者の情報を登録してもらう登録画面の2つです。
ホーム画面 | 登録画面 |
---|---|
システム構成
Route53でドメインをとったうえで、「必要なときに必要な程度稼働してくれる」実運用を考えながら、今回そこまでやる必要はないシステムを作ってます。先人たちの知恵借りまくりです。
Github
書いたコードは、以下の2つ。
システム | リンク |
---|---|
webページ | https://github.com/tyabata/web-invite |
api(lambda) | https://github.com/tyabata/lambda-api-invite |
個人情報とかcommitに入れちゃったのでgitのcommitログだけ消し去ってます
採用した技術
関連キーワードの羅列。詳細は次項から説明します。
Server Side
AWS
Lambda
DynamoDB
API Gateway
CloudFront
Route53
S3
理由
ちょっと前に自作IoTでGCP使ったので今回はAWS。
業務でスマートスピーカー開発してたときに触ったけど雰囲気でやってたので
復習でもしようかなという気持ちで選択しました。
Client Side
Nuxt.js
Vue.js
Vuex
Vuetify
TypeScript
axios
PWA
PostCSS
直近の業務で、React + Reduxを使っていたので今回はVue。
上記にいろいろ陳列してますがnuxt-ts
でほぼ全て用意してるので、PostCSS以外はだいたいコマンド一発。それが今年の1月末の頃…
nuxt-tsは2019/4/5ぐらいにnuxtに統合され 導入方法が変わっています
https://github.com/nuxt/nuxt.js/releases/tag/v2.6.0
Nuxt v2.4.0 is finally here 🎉
— Nuxt.js (@nuxt_js) 2019年1月28日
Official TypeScript support, smart prefetching and many more features & bug fixes.https://t.co/Ljf29xYvXi
「nuxtのconfigにtsの設定いれるのつら」
と思いながら格闘して環境構築完了した後日(2019/1末頃)、Nuxt公式が上をツイートしててnuxt-ts
を知り、結果的にはほぼ何もせず 「TypeScript」で「Vue+Vuex」を作る環境ができました。
型はいいぞぉ
型により構造の把握が楽になるし、ちゃんと書けてれば静的に問題に気付けるし以下略
開発手順概要
とりあえずAWSで登録をすませます
https://aws.amazon.com/jp/register-flow/
複数人で開発するならIAMとかで管理アカウントと分けましょう。と言いたいとこですが
今回は一人で かつ お仕事ではないので端折ります。
Server Side
まずは「参加者情報を登録するAPI」「Webサーバの代わりにs3を使う」という流れ
-
Route53
でドメイン取得 ->Certificate Manager
でSSL証明書取得 -
s3
準備 (linkのみ紹介) -
aws-cli
の導入 -
Lambda
準備からDynamoDB
にデータ登録まで -
API Gateway
とLambda
を接続 -
CloudFront
でS3
とAPI Gateway
のマルチオリジンにバックポストする際の設定
必要なとき以外、見る必要も見られることもない招待ページなので
コンピューティング時間を減らして省エネ運用の構成をとって…いるように見せかけてやたら色々準備したのは勉強がてら実際に使うことを考えた構成を目指してみたという具合です。
Client Side
Nuxt.jsを利用してページを開発 -> index.htmlを出力してS3にアップロードするまで
を以下の手順で説明していきます。
-
nuxt-ts
で開発できる下準備 - ページを作る
- index.htmlを出力する
- S3にアップロードする
開発詳細 : Server Side
ここからが本題です
ドメイン取得と証明書作成まで
Route53でドメイン取得
まずはドメインを取得します。
新しいドメインの登録 - Amazon Route 53
- https://console.aws.amazon.com/route53/ でRoute53を開く
- 登録方法
- 初めて : [Domain Registration] の [Get Started Now]を選択
- 二回目以降 : [Registered Domains]を選択
画像は省きますが、基本は同じ。
リンク先の説明にならって作業をすれば、自分のドメインがつくれます。
トップドメインによってお値段が異なるので、今回は安価でよくみる .net
を選択
- トップドメイン以降の自分でほしい名前を入力
- チェックの結果がOKであれば、それをカートに入れる (
Add to cart
) - 連絡先などを入力して進むと登録完了
- 登録したドメインが [Domain registration in progress]の状態からしばらく待つと[All Contacts]になれば完了。SOAレコードとかも同時に作成済みの状態になります。
ドメイン作成はここまでですが、これだけは登録したタイミングで課金が発生します。
Certificate Managerで証明書作成
さすがにwebエンジニアとしてhttpのリンクで友人だけでなく、嫁の知人含めて招待ページ登録してねー。と公開するのは社会の窓全開でご挨拶してる気がするのでサボらずちゃんと作成します。
初回は
[Provision certificates(証明書のプロビジョニング)] => [Get Started(今すぐ始める)] => [Request a Certificated(署名書のリクエスト)]
と選択していくと以下のような画面になると思います。
ドメイン名の入力欄に先程登録したドメインを入れましょう。
ここではワイルドカード証明書のリクエストもできるので、私は *.hoge.net
のような名前で証明書を作成しました。
この後、進めていくとドメイン所有者(つまり自分)に下記のようなメールが飛びます。
DomainやAccountIDや取得したRegionなどに問題がなければ、メールに記載されている
To approve this request, go to~
と書いてあるあたりのリンクから遷移して承認完了させます。
下記のような画面まで行けば、証明書の作成まで完了です。
実際に証明書を設定したりするのはCloudFrontあたりを扱う項へ。
S3の準備
ほか項目含めて全部書くと、やたら長いドキュメントになるので備忘録としてリンクだけ。
S3 バケットを作成する方法 - Amazon Simple Storage Service
バケット作成後に追加で設定したものは
- バージョニングの有効化 + ライフサイクルから旧バージョンに対する削除の設定
- 本番リリース後に問題発覚して戻すことがある場合、バージョン指定で戻せる
- しかし一度動いてしまえば1日以上たっても変わらない
- cliコマンドからのデプロイするために対象アカウントのみ書き込みを有効化
- それ以外の全ユーザーに対してはReadのみ有効
ライフサイクルの設定については、作成したバケットの上部にある「管理」タブから
[ライフサイクルルールの追加]という項目からできます。
上記通り、一時的なロールバックを考慮してバージョニングの有効化をしたので
一日以上たった過去バージョンは削除する。というライフサイクルを設定しました。
(結果的に不要でした)
アクセス権限については、CloudFrontからアクセスが前提なので
本当は全ユーザー有効設定ではなくCloudFrontからのアクセスに対してReadを与えるような設定が良いと思います。
…色々調べながらやってたので、全ユーザーがreadできる方が都合がよかったのです… w
(といってもさすがにURLは公開してないです)
とりあえずこのタイミングでは、表示確認のためのindex.html
に適当になんか書いたものをバケットのrootにおいといてください。
設定についてはこちらを参考にしてみてください。
CloudFront ディストリビューションからのみ S3 バケットへのアクセスを許可する
アクセス権限は適切にね!!
aws-cliの準備
Lambdaやs3にデプロイをするために、AWS用のコマンドラインツールであるaws-cli
からデプロイする準備をします。
IAMでユーザーの作成
aws-cliでアクセスする際に使うユーザー設定を行います。
admin使ってもできるんですが、お勉強とお作法的に分けます。
下記リンク等を参考に必要な情報を作成します。
最初の IAM 管理者のユーザーおよびグループの作成 - AWS Identity and Access Management
IAMから、左カラムのナビゲーションメニューから ユーザー
を選択し、上部にあるユーザーを追加
を選択します。
すると下の画像のように、ユーザーを追加するための設定画面が出てくるので、下の表のように設定していってください。
項目 | 設定 | 補足 |
---|---|---|
ユーザー名 | cli | ※なんでもOK |
アクセスの種類 | プログラムによるアクセス | |
グループの作成 | あとで説明 | 既存のポリシーを直接アタッチすることもできます |
タグ | なし | IAMの管理用です。個人開発で特にいらないので今回は省略 |
ここまで入力すると、確認の表示が出てくるので問題なければ次に進むと以下のようにアクセスキーとシークレットアクセスキーが取得できます。
どこかにメモっておきましょう。あとのcli設定に使います
IAMの概念(雑まとめ
グループ作成について説明をしていきます。
AWSに初めて触ると ロール
ユーザー
グループ
ポリシー
といろんな言葉がでてきて
チンプンカンプンになる(な気もしてる)ので、私の雑まとめです。
- ポリシーは 「AというポリシーはDynamoへのRead権限をもつ」といったルール的なもの
- ユーザー、グループ、ロールはそれぞれポリシーをアタッチして使う
- ユーザーやグループは人の管理に使う
- ロールはシステムで使う
という雰囲気理解です。とりあえず関係性が雰囲気でもわかればOKかと。
上の画像で線を足し忘れましたが、ユーザーにポリシーをアタッチできます。
が、複数で開発する場合は「開発」というグループをつくってユーザーをそこに紐づけていくことで、わざわざ個別にポリシーをアタッチする手間を一つにまとめられる利点等があると思います。
- Lambdaにデプロイするには
AWSLambdaFullAccess
ポリシー - S3にデプロイするには
AmazonS3FullAccess
ポリシー
といった具合で今回は
項目 | 設定値 |
---|---|
ユーザー名 | cli |
グループ名 | deployment |
ポリシー(グループに対して) | AWSLambdaFullAccess,AmazonS3FullAccess |
を設定しています。
「勉強だしとりあえず全権限ふっておけ」といった場合は
AdministratorAccess
を設定すればOKです。
仕事でやるなら用法用量はまもりまs(ry
aws-cli導入
brewで入れました。brew update行ってinstallから始めていきます。
brew install awscli
[~] aws --version 13:29:19
aws-cli/1.16.80 Python/3.7.1 Darwin/18.0.0 botocore/1.12.70
次にアクセスキーの設定をします。
[~] aws configure 14:52:40
AWS Access Key ID [****************XXXX]:
AWS Secret Access Key [****************XXXX]:
Default region name [us-east-1]:
Default output format [None]:
Access Key ID
や AWS Secret Access Key
は先程取得した値を設定してください。
regionはとりあえずTokyo(ap-northeast-1)とかでもいいと思います。
Outputは json
やText
とかありますが、これはご随意に(説明略)
[~] aws s3 ls 14:52:50
2018-12-24 20:36:54 hoge.xxxx.net
といったように確認コマンドで結果が返ってくればOKです。
LambdaへのdeployとdynamoDBの設定
とうとうコードがでてきますが、API側はだいぶシンプルに書いたつもりなので概要だけ。
https://github.com/tyabata/lambda-api-invite
APIのロジック
- Lambdaに対してリクエスト
-
/invitees/user
へのGET と/invitees
へのPUTを処理する - 実行例外は拾って、500を返すようにしている
-
-
/invitees/user
: GET- ページを初めて開いたときにアクセスされる。サーバ側でもつパスワードでcryptoした文字列をUserIDとして返す。UUIDは
crypto-js
を利用して時間から生成してる - ページ側がlocalstorageで保存しているので、消さない限りは再リクエストは発生しない(が、消されると新しいユーザーとして再度UUIDを発行します)
- webページ用にサーバを建てないようにした結果。こうなった
- ページを初めて開いたときにアクセスされる。サーバ側でもつパスワードでcryptoした文字列をUserIDとして返す。UUIDは
-
/invitees
: PUT- ページに登録した情報とUserIDをセットにして登録リクエスト
- UserIDが正しく複合できるか確認
- 問題なければ、登録情報が正しい値かチェックする
- すべてOKであればDynamoDBに登録
という流れ。
特に嫁側の方に登録時のハードルを上げないようにしたかったので、下を意識してざっくり作りました。
- ログインせずに登録できる。
- 一度画面閉じたあとに登録情報を更新できるようにidをもたせておきたい
- 私の友達が 絶対
いたずらデバッグするので簡単に登録APIを叩かれないようにした
lambdaへのデプロイ
シンプルなAPIで aws-cliもいれたので、package.jsonに以下のように記述
"zip": "zip lambda.zip -r node_modules src",
"first": "aws lambda create-function --function-name <lambdaに登録するfunction名> --zip-file fileb://lambda.zip --region <登録するlambdaのregion> --handler src/index.handler --runtime nodejs8.10 --role <登録するfunctionにアタッチするrole>"
これは新規登録用。nodeとかは古いので適当に置き換えてください
アタッチしているroleについては後で説明します。
コードができたら zip化
-> first
でアップロード&function作成
をしています。
"deploy": "npm run zip && npm run upload",
"upload": "aws lambda update-function-code --function-name <lambdaに登録したfunction名> --zip-file fileb://lambda.zip --profile cli --publish",
こちらは更新用
一つ上のzip化するコマンドにあわせて、awsにアップロードしてfunctionを更新するupload
コマンド。
そして、それらを一発で行うための deploy
コマンドです。
ここは特別に複雑なことはしてないです。
前項のaws-cli導入
でAWSLambdaFullAccess
がポリシーとしてアタッチされている状態であれば、これで新規登録や更新が完了します。
Lambdaで登録したregionで開くと、上の通りに登録されていることが確認できると思います。
そしてLambdaにアタッチしているroleですが、 CloudWatchにログを流す
DynamoにアクセスしてR/Wする
といったポリシーをふったroleをアタッチしています。
詳細説明は省きます
ただ、Lambdaのログを流すために設定しておくことで、問題があったときにも気づけるので登録しておきましょう。
Amazon CloudWatch Logs とは - Amazon CloudWatch Logs
DynamoDBへ登録
DynamoDBにデータを登録していく準備をします。
上記のリンクからテーブルの作成
を選び、テーブル名やプライマリキーを設定して作成を押します。
今回は、前項あたりで説明した UserIDをプライマリキーに設定するため、uid:文字列
として設定しました。
…うろ覚えですが、コレ以上はDynamoDBでの設定はなかったはずです。
コード上ではここらへん
https://github.com/tyabata/lambda-api-invite/blob/master/src/dynamo.js#L30
普通のDBみたいにテーブル定義がどうとか行わなくても、データをputすればそのobjectの要素通りにカラムを自動で作ります。そこらへんはお手軽。
ただ、PUTするときに空文字とかを入れようとするとエラーになるので、チェックして弾いてあげるか
設定されていないときのデフォルト値が必要であれば、登録前に足してあげましょう。
const AWS = require('aws-sdk');
// 登録先のregionを設定する
AWS.config.update({
region: 'us-east-1'
});
const docClient = new AWS.DynamoDB.DocumentClient();
docClient.put(
{
// テーブル名
TableName: "table名をここに",
Item: item
},
(error, data) => { // 割愛
コード概要はこんな感じです。登録などに必要な権限的なものは、実行しているLambdaにアタッチされているロールのポリシーに依存しています。
前項でIAMでユーザーを作ったときと同様に
- IAMの左カラムの
ロール
からロールを作成
- ロールを使用するサービスとして Lambdaを選択
- ロールにアタッチするポリシーとして
AmazonDynamoDBFullAccess
をセット - タグは任意(管理用です)
- 最後にロール名とロールの説明を書いて完了。
ちなみにFullAccessはいらないぜ。といった場合は、他にもロールがあるのですが適切なものがない場合は独自でポリシーを作成することもできます。が、これもここでは省略します。
動作確認
とりあえずここまでちゃんと設定できているか確認のためにテストしてみましょう。
直接リクエスト送るには、この先のAPI Gatewayへの登録などが必要になります。
しかし、Lambdaのテストを使って、試しに処理を実行させることは可能です。
Lambdaの関数の画面から、右上のテストボタンの左にあるプルダウンを選択しテストイベントの設定
を押します。
そこで、画像のようにhandlerが受け付けたときに処理するbodyを用意してあげることで 疎通確認とDynamoへの登録の確認ができると思います。
API GatewayとLambdaを接続
API Gatewayを使い、そのバックポスト先として先程作成したLambdaのfunctionのARN(Amazon Resource Name)を指定することで、外からhttpリクエストでLambdaを実行できるようになります。
API Gatewayを選択すると、下のように新規作成時の画面が表示されます。
ここではREST APIと 新しいAPIを選択し、API名を入力して作成を開始します。
APIのパスとメソッドの設定
API作成後に上記のような画面になるので `アクション`から`リソースの作成`を選択します。- リソース名 : 適切な名前
- リソースパス : 今回は
/invitees
をすることで、リソース一覧にパスが追加されます。
/invitees
以下にパスを設定する場合は選択した状態で、上のようにアクション
から作成を同じ手順で行います。
リクエストメソッドを足す場合も アクション
からメソッドの作成
を選ぶと、一覧に新しいプルダウンが表示されるのでメソッドを選ぶと、下記のような画面が表示されるので
今回は 総合タイプにLambda関数
。Lambda関数に 先程作成したLambda functionのARNを設定してください。
ARNは、Lambdaのfunctionの画面右上にある文字列で
arn:aws:lambda:us-east-1:111111111111:function:invitees_prod
みたいな形式のものです。
その他、regionなどはLambdaにあわせて設定して保存を押すと作成完了です。
リクエスト/レスポンスの詳細設定
ここから、API Gatewayの詳細を設定していきます。
今回は作ったもののうち、PUT : /invitees
について説明していきます
項目 | 概要 |
---|---|
メソッドリクエスト | 作成したタイミングの値が入ってる。今回はこのまま |
総合リクエスト | リクエスト情報の制御的なところ。マッピングテンプレートをいじります(後ほど) |
Lambda | 設定されているLambda functionの名前が表示されていればOK |
総合レスポンス | 返すレスポンスを制御するところ。ここに400や500のパターンを追加する(後ほど) |
メソッドレスポンス | こちらも200, 400, 500の3つを追加しておく |
リクエストのマッピングについて
コレはパッと身よくわからなくてつらかったので、ここでは概要だけ。
詳細はリファレンスや先人の知恵をおかりするのをオススメします。
API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway
上記で設定した項目のうち 総合リクエストのを選択し、下部にあるマッピングテンプレートの項目を選びます。
「テンプレートが定義されていない場合」を選択し、Content-Type
にapplication/json
を選びます。
追加すると、さらに下にテンプレートを入力する欄が表示されるので、今回は下のように入力しました。
{
"method": "$context.httpMethod",
"body" : $input.json('$'),
"path" : "$context.resourcePath",
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))"
#if($foreach.hasNext),#end
#end
}
}
とりあえずわからなくてもいいです。私もドキュメントにならった説明しかできなないです。
拡張したくなったらリファレンスを読みましょう
雑にいうとAPI Gatewayで受けたリクエストを展開してLambdaのコードで受けやすい形に変換してるといったところになると思います。
上のようにテンプレートにはめ込んだものを handlerの第一引数で受け取って処理を実行しています。
https://github.com/tyabata/lambda-api-invite/blob/master/src/index.js#L22
レスポンスのマッピングについて
Lambdaがいくらエラーメッセージを返しても、API Gatewayは200を返してしまいます。
そこで、特定の文字列が含まれるときは 400
や500
で返せるように設定します。
ココらへんはシンプルに
.*"status" *: *400.*
という文字列がレスポンスに含まれていれば 400を返す
といった設定です。500も同様。
次に一つページをもどって、全体の画面からメソッドレスポンスを選択し、HTTPのステータスという項目に400や500などを追加します。
これで、リクエストに対して適切なステータスコードを返せるようになります。
APIのデプロイ
忘れがちですが、これをやらないと反映されないので、メソッド作成等と同様にアクションから APIのデプロイを押しましょう。
デプロイ対象のステージが表示されるので、選択してデプロイ
を押すことで初めて反映されます。
…ステージの説明を書き忘れていましたが、コンソールの左にAPIごとにステージという項目があり
ソレを選択して、 beta
とかprod
とかステージを分けておくと、開発環境と本番環境を分けておくことができます。
例えば beta
ステージの場合は、下のようなパスになります。
https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/beta
動作確認
最後に動作確認として、メソッドの実行画面の左側にある 雷マークがついたテスト
を選択してテストを行います。
メソッドテストと表示されている画面に遷移したら、一番したのリクエスト本文
の項目に
リクエストするbodyの情報をセットします。
右側に実行時のログが表示されます。ただしく設定ができていれば、成功時のレスポンス内容がログに表示されると思います。
CloudFrontでS3とAPI Gatewayにバックポストする
やっとServer Sideの説明の最後に来ました…! 総集編?です。
取得したドメインを設定したCloud Frontでリクエストを受け取って、s3からhtmlを返すか、APIにリクエストするかをパスによってバックポストする設定を追加していきます。
2つのオリジンの追加
-
Cloud Frontを開いて、
Create Distribution
から作成 -
Web
のGet Started
を選択 -
Origin Domain Name
にAPI Gatewayの設定を追加していきます。 -
https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/beta
というURLなので下の通りに設定する-
Origin Domain Name
xxxxxxxx.execute-api.us-east-1.amazonaws.com
-
Origin Path
/beta
- Origin IDは勝手に入力されるやつのまま
-
Origin Protocol Policyは
HTTPS Only
-
Origin Domain Name
- Behavior Settingsは以下の通り
-
Viewer Protocol Policy
Redirect HTTP to HTTPS
- Allowed HTTP Methods GETとPUTが選択できる一番下の項目
-
Viewer Protocol Policy
- Distribution Settings
-
Default Root Object :
http:web.xxx.net/
にアクセスしたときに/index.html
にアクセスするように設定する
-
Default Root Object :
何か漏らしてる気がするけど、これで一旦作成を完了させる。
これでhomeに新しく作成されたDistributionが表示される。
作成完了には少し時間がかかりますが、作成が完了したら次に Create Origin
でs3の設定を足していきます
-
Create Origin
を選択 - Origin Domain Name でs3のオリジンを選択する(サジェストで表示されます)
-
Origin Access Identityで
Create a New Identiy
を選択する。(理想 : こうすることで、s3へのアクセスをcloud frontのみにできる。が、今回はやってないです)
これでs3のオリジンも追加しました。
アクセスしたパスでバックポストするオリジンを分ける
次に Origins and Origin Groups
の隣の Behaviors
タブを選択して
Create behavior
を追加します。
今回は、jsやcssなどをs3に置くので API以外のパスはすべてs3に流れるように設定します
で設定したのが以下の2つのパス
-
Default(*)
: S3にバックポストする-
Viewer Protocol Policy は
Redirect HTTP to HTTPS
-
Allowed HTTP Methodsは
GET HEAD
-
Cache Based on Selected Request Headersは whitelist
- Originとリクエスト情報だけforwardする(しなくてもいいけど)
-
Viewer Protocol Policy は
-
invitees/*
: API Gateway にバックポストする- GETとPUTは受け付けられるように
-
Cache Based on Selected Request Headersは whitelist
- Originと一応Authorizationだけforwardしてる
- キャッシュがじゃまになるAPIしかないので、cacheの設定は問題ないように修正。
CNAMEや証明書の設定
最初に作った証明書をやっと使うときが来ました。
DistributionのGeneral
タブから、Editボタンを押し設定を編集していきます。
- CNAME(Alternate Domain Names) :
web.xxxx.net
といった証明書にあわせて自分がつけたホスト名を設定 - SSL Certificate :
Custom SSL Certificate
と書いてるほうを選択して先程作成した証明書を選択する。
ソレ以外はだいたいデフォの設定のままでOK
おまけ Error Pageを設定
最後におまけで、Error Pageの設定をします。
ほぼ全部のパスがs3に流れるが、適当なパスを入れるとs3のエラー画面が表示されてしまいs3使ってる感がモロバレなので、設定をします。
Create Custom Error Responseを選択して
-
HTTP Error Code :
403
- Error Caching Minimum TTL : よしなに
-
Customize Error Response :
YES
-
Response Page Path :
/error.html
-
HTTP Response Code :
404
-
Response Page Path :
s3がなにもないパスにアクセスしたときに403を返すので、これで代わりにerror用のhtmlを表示させることができます。
動作確認
おつかれさまでした!備忘録的に書いてるので何か漏らしてる気がしますが、
これで問題がなければ 設定したドメインでアクセスしたら s3作成時においた index.html
が表示でき、APIのパスにアクセスすればAPIの結果が返ってくるようになるはずです。
…もし動かなかったらごめんなさい。
ググれば多分解決できる程度にn番煎じネタではあると思います…たぶん
開発詳細 : Client Side
とりあえず一息ついたら次に表示系の開発をしていきましょう。
Serverと比べるとだいぶ楽かもです。
一応補足
https://github.com/nuxt/nuxt.js/releases/tag/v2.6.0
この記事は、2019/04/05にv2.6がでるより前に作ったものの備忘録です。
一応 nuxt-tsを削除してnuxtに置き換えてみたので、気になったら下の変更箇所を見てみてください
https://github.com/tyabata/web-invite/commit/6d99fbbdde76b84b6c8baa4d1c7b73f3a5683afa
NuxtとTypeScriptの開発環境を構築する
ここは、僕が2019/2頃にやった nuxt-ts
もすでに古いので、公式や他の皆々様が書いている記事をご覧になる方が良いと思います
https://ja.nuxtjs.org/guide/typescript/
create-nuxt-app
で作るところは一緒で下の設定になります。
(対話式で導入するモジュールを選んでいきます)
> Generating Nuxt.js project in /Users/xxxxx/Documents/workspace/vscode/sample
? Project name sample
? Project description My sensational Nuxt.js project
? Use a custom server framework none
? Choose features to install Progressive Web App (PWA) Support, Linter / Formatter, Prettier, Axios
? Use a custom UI framework vuetify
? Use a custom test framework jest
? Choose rendering mode Single Page App
? Author name xxxxx
? Choose a package manager yarn
TypeScript対応はここから下を入れてjsをtsに入れ替えていく作業になると思います。
yarn add ts-node @nuxt/typescript
簡単なページはこれで完結できるのですが
webpackがラップされてる感じなのでアレコレ拡張するのが煩わしく感じることもあると思うので、プロダクトにあわせて使う使わないは判断すれば良いかなと思います。
ページを作る
といっても、基本はVueとVuexです。
- Nuxtにのっとって書く
- axiosで用意したAPIと通信をする
- Vuetifyで基本的なデザイン作成
- ロジックの分離を意識する
といったぐらいのことしか…やってないかも
とりあえず書きます!
create-nuxt-appから少しだけ設定を変える
create-nuxt-app
をつくると、ルートにstore
やpage
といったフォルダができます。
ちょっぱやで作る分にはいいのですが、気になる人(自分含む)は
例えばsrc
というフォルダ以下に移したい場合はsrcDir
というフィールドに設定します。
const config: NuxtConfiguration = {
srcDir: 'src/',
mode: 'spa',
...
また、cssを書く用にPostCSSを入れたかったので
ここに、ページで使うcssを設定
https://github.com/tyabata/web-invite/blob/master/nuxt.config.ts#L47
そしてnuxt.config.js
のbuild以下に下のように設定を入れました。
build: {
extend(config: any, context: any) {
},
cssSourceMap: true,
postcss: {
plugins: {
'postcss-import': {},
'postcss-mixins': {},
'postcss-preset-env': {},
'postcss-nested': {},
// css minify
csswring: {}
}
}
}
これで設定は完了です。このアプリではsrc/assets/postcsss
以下にPostCSSを使ったcss郡がおいてあります。
…post cssをassetは違う気がするな
ちなみに テンプレのvueファイルでPostCSSをつかった記述をする場合は下の通りにstyleタグを足します
<template>
</template>
<script lang="ts">
</script>
<style lang="postcss" scoped>
@import '@/assets/postcss/invite.css';
</style>
Vuetifyをつかってレイアウトを作る
Vue.js Material Component Framework — Vuetify.js
今回作ったページはほぼほぼVuetifyの力をつかって調整していて、cssを使ったのは微調整ぐらいでした。
Vueでマテリアルデザインに沿ったレイアウトを作るためのいろんな機能を提供してくれるので
基本的にはタグを埋め込んで終わり。という感じです。
一応元Android開発をしていたので、ToolbarとかSnackbarとかきいて何かわかりますが、聞き馴染みのない方には最初苦労するかもしれません(主に検索で)
比較的公式のドキュメントもまとまっている……と思います。
ページのコンポーネント構造について
そもそも今回作ったコンポーネントの構造を雑に説明するとこんな感じです。
<template>
<v-app>
<nuxt/>
</v-app>
</template>
上はおまじない的なやつです。vuetifyに欠かせない部分になります。
このテンプレートをベースに、index.vueはつくっています
<template>
<v-container>
<!-- 下タブの選択によってアニメーションしながら切り替えるためのコンポーネント -->
<v-window />
<v-snackbar />
<v-bottom-nav />
</v-container>
</template>
レスポンシブなページの対応はだいたいVuetifyがやってくれるのでcssはほぼ何もがんばりません。
また、v-bottom-nav
などは、ページに下部に固定できたり、カードや検索窓なども提供されているので、アプリっぽいwebページを作るにはとても強力なツールかなと思いました(雑感)
コンポーネント紹介
v-window
Windows — Vuetify.js
ページ遷移のトランジションが簡単にできちゃうコンポーネント
<v-window v-model="activePage" :touchless="true">
<v-window-item :value="'home'">
<home/>
</v-window-item>
<v-window-item :value="'register'">
<register/>
</v-window-item>
<v-window-item :value="'other'">
<other/>
</v-window-item>
</v-window>
デフォルトは、スワイプによる画面遷移が備わっていますが
調整する暇もないし、タブによるアクションのみにするため
touchless = true
としています。
あとは上の通りですが、activePage
に入ってる値によって出す表示を切り替えているだけです。
ページによって要素の高さが違うと思うので、遷移時に気になる場合はscroll位置の調整などもしてみてください。
ロジックの分離について
今回の要件だけならVueだけでもよいのですが、NuxtでVuexが簡単に入れられるので
意識して実装してみました。
公式の図からVuexにおけるフロー図です。
このwebページでは、ducksパターンでstoreを作成しています。
React + Reduxでもそうなのですが、action type
やaction
やreducer(Redux)
mutation(Vuex)
をそれぞれ分けると管理がつらいので、どうせそれぞれが密なものであれば一つのファイルでまとめよう。っていうデザインパターンです。
あとは
- Vueでイベントがあれば、Actionを呼ぶ
- Actionごとの処理をする
- 処理後に状態の変更をcommitをしてstateに反映する
- Vue側で新しい状態を反映する
という流れにそって書きました。
それぞれが、役割以上のロジックを持たないようにしましたが
今回はそれとあわせて一つ意識していることについて書いておきます。
「templateは状態の変え方をしらない」ようにする
Smart UIにならないようにしましょうというやつです。
Atomicデザインを目指したり目指さなかったりしても、可能な限りViewで表示以外のロジックを持つべきではないと思います。
例えば Vueファイルのクラス内で、ボタンが押されたときに次のページに遷移するという処理のために
this.$store.dispatch('goToNextPage', current + 1)
という書き方をしたこともあると思いますが、一度ここで立ち止まって考えてみましょう。
本当に次のページは「currentに1を足した値」でいいのか
「そりゃ1ページの次は2ページでしょ?」といえば普通なのですが、それはあくまで現実の話で
コードに落とし込んだときに、この書き方は「Viewが表示の変え方を**+1すること**と知ってしまっている」状態になります。
もしかしたら、文字列で管理されているかもしれません。
例えばですが、表示系からは「次のページへいく」という振る舞いだけ呼び出し
this.$store.dispatch('goToNextPage')
そして、Actionで以下のように「次のページは、今のページ + 1である」という振る舞いの詳細を定義する。
goToNextPage(context: ActionContext<IState, any>, payload: any) {
const nextPage = context.state.current + 1;
...
という書き方で、可能な限りViewからロジックを剥がすことで
そのコンポーネントの再利用性が高まっていくと思います
余談ですが、ReduxでContainer Componentで書いたとき、connectの第三引数の mergeProps
を使うことで、テンプレート側に状態を渡さずに dispatchイベントで現在の状態を知る。といった書き方もできるので、「テンプレート側に値を渡してたぜ!」という方はぜひぜひ。
import { connect } from 'react-redux'
// 色々省略
export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps // これ
}(component)
redux connect mergePropsとか調べれば色々出てくるかと思います!
react-redux/connect.md at master · reduxjs/react-redux
デプロイをする
最後の工程です。
index.htmlの出力 と s3のアップロードについて書いていきます。
htmlの出力
今回はサーバまわりを書いてないですが
create-nuxt-app
で作っている場合、/pages
以下のvueファイルの名前にならってエントリポイントが作成されます。
そして、nuxt generate
コマンドはそのエントリでアクセスしたときに表示されるhtmlをファイルとして出力します。
/pages
- index.vue
- error.vue
↓このようなファイルをおいている場合、上のコマンドを実行すると
dist/index.html
dist/error.html
というファイルと関連するcssやjsが出力されていると思います。
こちらをs3にアップロードしていきます。
generateの結果をs3へアップロードする
とうとう最後の作業です。以下のようなコマンドをpackage.jsonのscriptに足しています。
"upload": "aws s3 sync ./dist s3://${npm_package_config_bucket} --include \"*\" --acl public-read --cache-control \"max-age=900\" --profile=cli",
ビルドする内容が複雑になりすぎて、scriptに書きたくない量になりそうなら
適宜いい感じにgulpとか使ってあげてください。
npm configの変数については後述しています。
まずaws s3 sync
コマンドで、指定のフォルダをまるっとs3の特定バケットにアップロードしています
aws s3 sync ./dist s3://${npm_package_config_bucket}
ソレ以外のオプションについては↓のとおりです。
option | 説明 |
---|---|
include | excludeとあわせて使うオプションです。今回は不要で実はミスしていました |
acl | s3においたコンテンツに対するACL設定です。 public-read で全Userが参照可能になります |
cache-control | s3においたコンテンツにCache-Controlを設定します |
profile | aws-cliにdefault以外のユーザーを設定してるとき、コマンドを実行するユーザー設定を指定します |
不要なファイルを上げないようにexcludeといったオプションも細かく設定していくべきなときもありますが、今回は特に気にせず nuxt generate
した結果を全部アップロードしています。
これで、全てが完了です(たぶん…)
おまけ: npm configで公開したくない値をscript上で変数化する
ついでですが npm config
を使ってgit上にバケット名を公開しないようにしています。
privateな場所であれば気にしなくてもいいですが、今回は最終的にpublicなリポジトリに公開をするため、npm config
でscript上で使われる値をセットできます。
例としてpackage.json
に定義したname
がappname
の場合
npm config set appname:bucket <値>
とsetするとscriptで
"hoge": "echo ${npm_package_config_bucket}"
のように npm_package_config_<変数名>
形でscript上で展開することもできます。
動作確認
ここまでやったすべての確認をしていきましょう。
- 作成したドメインでアクセスができた
- 作成したドメインのルートにアクセスしたら index.htmlにアクセスするようになっているので、s3にデプロイしたindex.htmlが表示された
- 初回アクセスで、userIdを取得するために /inviteesのパスにリクエストして、レスポンスが返ってきた
- 入力フォームに値を入力してOKを押したら登録成功した
- DynamoDBに登録した情報が表示されている
備忘録的に順にかいてますが、3月前の記憶がベースなのでもしかしたら足りない情報があるかもしれません…
もし失敗してCloudFrontにキャッシュ設定をしたせいでs3を更新しても反映されない。
といった場合、CloudFrontの画面から Invalidation
のタブを選択して、Create Invalidation
のボタンを押すとパスを入力する画面が表示されると思います。
こちらの入力欄に/*
と入れてInvalidate
を実行すると まるっとキャッシュを消してくれます(実行完了に少しだけ時間がかかります)
注意 やりすぎると課金にかかわるので、やりすぎないかお財布と相談してください
まとめ
言い訳になりますが、2019/2末ぐらいには開発完了していてそれを出してから放置をしていたため、この備忘録も当時の記憶を頼りに書いてるのでだいぶヌケモレあるんじゃないかな…と思います。
フロントにいたっては、あまり特殊実装もしてないのであとは「コード見て」状態になってますね…/(^o^)\
多分おかしいところあるきがするのでアレば教えてください
とりあえず整理もせずひたすら書いたのでクソ長いですが、
もしここまで読んでくださった方がいたら本当にありがとうございました!
会社だと整った環境があるので、なかなか1から作ることはないからこそ
お勉強としてやってみましたが…それでもAWSで完結するんだから楽ですよね
パーティも終わってこれも書き終わったのでAWSの解約をして
趣味のゲームづくり(Unity)に力を注いでいきたいなという そんな今日このごろ