32
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Nuxt.jsとAWSで招待状webページを作ったまとめ

Last updated at Posted at 2019-06-26

概要

2018年に結婚しました!ので!
ここはエンジニアらしくパーティ招待状Webページをつくってみることにしたのが始まりです。

とりあえず無事にパーティも終わったので、開発時の記憶をさかのぼりながら残す備忘録ですが
申請から開発まで一通り殴り書くので、何かしら参考になれば幸いです。┗(^o^)┛
…ちょっと前の記憶を掘り起こしながらやるので、間違って書いてそうなところもある気がしますが温かい目で御覧ください :bow:

開発環境はMacなので、Winの方は適宜読み替えていただけると幸いです :pray:
AWS上で日本語表示できている部分は、日本語の画面で説明してる…はずです

成果物

webページ

ざっくり3つの画面構成です。(3つ目はフリー素材やOSSの情報なので割愛)
GoogleMapや開催日を記載したホーム画面と、実際に参加者の情報を登録してもらう登録画面の2つです。

ホーム画面 登録画面
スクリーンショット 2019-03-06 13.52.42.png スクリーンショット 2019-03-06 15.38.07.png

システム構成

今回のwebページのシステム概要はこんな感じ

Route53でドメインをとったうえで、「必要なときに必要な程度稼働してくれる」実運用を考えながら、今回そこまでやる必要はないシステムを作ってます。先人たちの知恵借りまくりです。

Github

書いたコードは、以下の2つ。

システム リンク
webページ https://github.com/tyabata/web-invite
api(lambda) https://github.com/tyabata/lambda-api-invite

個人情報とかcommitに入れちゃったのでgitのcommitログだけ消し去ってます :innocent:

採用した技術

関連キーワードの羅列。詳細は次項から説明します。

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のconfigにtsの設定いれるのつら」
と思いながら格闘して環境構築完了した後日(2019/1末頃)、Nuxt公式が上をツイートしててnuxt-tsを知り、結果的にはほぼ何もせず 「TypeScript」で「Vue+Vuex」を作る環境ができました。

型はいいぞぉ
型により構造の把握が楽になるし、ちゃんと書けてれば静的に問題に気付けるし以下略

開発手順概要

とりあえずAWSで登録をすませます
https://aws.amazon.com/jp/register-flow/

複数人で開発するならIAMとかで管理アカウントと分けましょう。と言いたいとこですが
今回は一人で かつ お仕事ではないので端折ります。

Server Side

まずは「参加者情報を登録するAPI」「Webサーバの代わりにs3を使う」という流れ

  1. Route53でドメイン取得 -> Certificate ManagerでSSL証明書取得
  2. s3準備 (linkのみ紹介)
  3. aws-cliの導入
  4. Lambda準備からDynamoDBにデータ登録まで
  5. API GatewayLambdaを接続
  6. CloudFrontS3API Gatewayのマルチオリジンにバックポストする際の設定

必要なとき以外、見る必要も見られることもない招待ページなので
コンピューティング時間を減らして省エネ運用の構成をとって…いるように見せかけてやたら色々準備したのは勉強がてら実際に使うことを考えた構成を目指してみたという具合です。

Client Side

Nuxt.jsを利用してページを開発 -> index.htmlを出力してS3にアップロードするまで
を以下の手順で説明していきます。

  1. nuxt-tsで開発できる下準備
  2. ページを作る
  3. index.htmlを出力する
  4. S3にアップロードする

開発詳細 : Server Side

ここからが本題です

ドメイン取得と証明書作成まで

Route53でドメイン取得

まずはドメインを取得します。
新しいドメインの登録 - Amazon Route 53

画像は省きますが、基本は同じ。
リンク先の説明にならって作業をすれば、自分のドメインがつくれます。
トップドメインによってお値段が異なるので、今回は安価でよくみる .netを選択

  • トップドメイン以降の自分でほしい名前を入力
  • チェックの結果がOKであれば、それをカートに入れる (Add to cart)
  • 連絡先などを入力して進むと登録完了
  • 登録したドメインが [Domain registration in progress]の状態からしばらく待つと[All Contacts]になれば完了。SOAレコードとかも同時に作成済みの状態になります。

ドメイン作成はここまでですが、これだけは登録したタイミングで課金が発生します。

Certificate Managerで証明書作成

さすがにwebエンジニアとしてhttpのリンクで友人だけでなく、嫁の知人含めて招待ページ登録してねー。と公開するのは社会の窓全開でご挨拶してる気がするのでサボらずちゃんと作成します。

初回は
[Provision certificates(証明書のプロビジョニング)] => [Get Started(今すぐ始める)] => [Request a Certificated(署名書のリクエスト)]
と選択していくと以下のような画面になると思います。

image.png

ドメイン名の入力欄に先程登録したドメインを入れましょう。
ここではワイルドカード証明書のリクエストもできるので、私は *.hoge.netのような名前で証明書を作成しました。
この後、進めていくとドメイン所有者(つまり自分)に下記のようなメールが飛びます。
image.png

DomainやAccountIDや取得したRegionなどに問題がなければ、メールに記載されている
To approve this request, go to~と書いてあるあたりのリンクから遷移して承認完了させます。
下記のような画面まで行けば、証明書の作成まで完了です。
image.png

実際に証明書を設定したりするのはCloudFrontあたりを扱う項へ。

S3の準備

ほか項目含めて全部書くと、やたら長いドキュメントになるので備忘録としてリンクだけ。
S3 バケットを作成する方法 - Amazon Simple Storage Service

バケット作成後に追加で設定したものは

  • バージョニングの有効化 + ライフサイクルから旧バージョンに対する削除の設定
    • 本番リリース後に問題発覚して戻すことがある場合、バージョン指定で戻せる
    • しかし一度動いてしまえば1日以上たっても変わらない
  • cliコマンドからのデプロイするために対象アカウントのみ書き込みを有効化
  • それ以外の全ユーザーに対してはReadのみ有効

ライフサイクルの設定については、作成したバケットの上部にある「管理」タブから
[ライフサイクルルールの追加]という項目からできます。
上記通り、一時的なロールバックを考慮してバージョニングの有効化をしたので
一日以上たった過去バージョンは削除する。というライフサイクルを設定しました。
(結果的に不要でした)

image.png

アクセス権限については、CloudFrontからアクセスが前提なので
本当は全ユーザー有効設定ではなくCloudFrontからのアクセスに対してReadを与えるような設定が良いと思います。
…色々調べながらやってたので、全ユーザーがreadできる方が都合がよかったのです… :bow: 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から、左カラムのナビゲーションメニューから ユーザーを選択し、上部にあるユーザーを追加を選択します。
すると下の画像のように、ユーザーを追加するための設定画面が出てくるので、下の表のように設定していってください。
image.png

項目 設定 補足
ユーザー名 cli ※なんでもOK
アクセスの種類 プログラムによるアクセス
グループの作成 あとで説明 既存のポリシーを直接アタッチすることもできます
タグ なし IAMの管理用です。個人開発で特にいらないので今回は省略

ここまで入力すると、確認の表示が出てくるので問題なければ次に進むと以下のようにアクセスキーとシークレットアクセスキーが取得できます。
どこかにメモっておきましょう。あとのcli設定に使います

image.png

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 IDAWS Secret Access Keyは先程取得した値を設定してください。
regionはとりあえずTokyo(ap-northeast-1)とかでもいいと思います。
Outputは jsonTextとかありますが、これはご随意に(説明略)

[~] 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ページ用にサーバを建てないようにした結果。こうなった :innocent:
  • /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とかは古いので適当に置き換えてください :bow:
アタッチしている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がポリシーとしてアタッチされている状態であれば、これで新規登録や更新が完了します。

image.png
Lambdaで登録したregionで開くと、上の通りに登録されていることが確認できると思います。

そしてLambdaにアタッチしているroleですが、 CloudWatchにログを流す DynamoにアクセスしてR/Wするといったポリシーをふったroleをアタッチしています。

詳細説明は省きます :bow:
ただ、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のテストを使って、試しに処理を実行させることは可能です。

image.png
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にあわせて設定して保存を押すと作成完了です。

image.png

リクエスト/レスポンスの詳細設定

ここから、API Gatewayの詳細を設定していきます。
今回は作ったもののうち、PUT : /inviteesについて説明していきます

項目 概要
メソッドリクエスト 作成したタイミングの値が入ってる。今回はこのまま
総合リクエスト リクエスト情報の制御的なところ。マッピングテンプレートをいじります(後ほど)
Lambda 設定されているLambda functionの名前が表示されていればOK
総合レスポンス 返すレスポンスを制御するところ。ここに400や500のパターンを追加する(後ほど)
メソッドレスポンス こちらも200, 400, 500の3つを追加しておく

リクエストのマッピングについて

コレはパッと身よくわからなくてつらかったので、ここでは概要だけ。
詳細はリファレンスや先人の知恵をおかりするのをオススメします。
API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway

上記で設定した項目のうち 総合リクエストのを選択し、下部にあるマッピングテンプレートの項目を選びます。

「テンプレートが定義されていない場合」を選択し、Content-Typeapplication/jsonを選びます。

image.png

追加すると、さらに下にテンプレートを入力する欄が表示されるので、今回は下のように入力しました。

{
    "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
    }
}

とりあえずわからなくてもいいです。私もドキュメントにならった説明しかできなないです。
拡張したくなったらリファレンスを読みましょう :innocent:
雑にいうとAPI Gatewayで受けたリクエストを展開してLambdaのコードで受けやすい形に変換してるといったところになると思います。

上のようにテンプレートにはめ込んだものを handlerの第一引数で受け取って処理を実行しています。
https://github.com/tyabata/lambda-api-invite/blob/master/src/index.js#L22

レスポンスのマッピングについて

Lambdaがいくらエラーメッセージを返しても、API Gatewayは200を返してしまいます。
そこで、特定の文字列が含まれるときは 400500で返せるように設定します。

統合レスポンスを選択します
スクリーンショット 2019-06-23 18.04.22.png

ココらへんはシンプルに
.*"status" *: *400.*という文字列がレスポンスに含まれていれば 400を返す
といった設定です。500も同様。

次に一つページをもどって、全体の画面からメソッドレスポンスを選択し、HTTPのステータスという項目に400や500などを追加します。
これで、リクエストに対して適切なステータスコードを返せるようになります。

APIのデプロイ

忘れがちですが、これをやらないと反映されないので、メソッド作成等と同様にアクションから APIのデプロイを押しましょう。
デプロイ対象のステージが表示されるので、選択してデプロイを押すことで初めて反映されます。

image.png

…ステージの説明を書き忘れていましたが、コンソールの左にAPIごとにステージという項目があり
ソレを選択して、 betaとかprodとかステージを分けておくと、開発環境と本番環境を分けておくことができます。

例えば betaステージの場合は、下のようなパスになります。
https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/beta

スクリーンショット 2019-06-23 19.31.37.png このURLはCloud Frontの設定でも使います。

動作確認

最後に動作確認として、メソッドの実行画面の左側にある 雷マークがついたテストを選択してテストを行います。
メソッドテストと表示されている画面に遷移したら、一番したのリクエスト本文の項目に
リクエストするbodyの情報をセットします。

右側に実行時のログが表示されます。ただしく設定ができていれば、成功時のレスポンス内容がログに表示されると思います。

CloudFrontでS3とAPI Gatewayにバックポストする

やっとServer Sideの説明の最後に来ました…! 総集編?です。

取得したドメインを設定したCloud Frontでリクエストを受け取って、s3からhtmlを返すか、APIにリクエストするかをパスによってバックポストする設定を追加していきます。

2つのオリジンの追加

  • Cloud Frontを開いて、Create Distributionから作成
  • WebGet 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 PolicyHTTPS Only
  • Behavior Settingsは以下の通り
    • Viewer Protocol Policy Redirect HTTP to HTTPS
    • Allowed HTTP Methods GETとPUTが選択できる一番下の項目
  • Distribution Settings
    • Default Root Object : http:web.xxx.net/にアクセスしたときに/index.htmlにアクセスするように設定する

何か漏らしてる気がするけど、これで一旦作成を完了させる。
これでhomeに新しく作成されたDistributionが表示される。
作成完了には少し時間がかかりますが、作成が完了したら次に Create Originでs3の設定を足していきます

image.png

  • Create Originを選択
  • Origin Domain Name でs3のオリジンを選択する(サジェストで表示されます)
  • Origin Access IdentityCreate a New Identiyを選択する。(理想 : こうすることで、s3へのアクセスをcloud frontのみにできる。が、今回はやってないです)

これでs3のオリジンも追加しました。

アクセスしたパスでバックポストするオリジンを分ける

次に Origins and Origin Groupsの隣の Behaviorsタブを選択して
Create behaviorを追加します。

今回は、jsやcssなどをs3に置くので API以外のパスはすべてs3に流れるように設定します

で設定したのが以下の2つのパス

  • Default(*) : S3にバックポストする
    • Viewer Protocol PolicyRedirect HTTP to HTTPS
    • Allowed HTTP MethodsGET HEAD
    • Cache Based on Selected Request Headersは whitelist
      • Originとリクエスト情報だけforwardする(しなくてもいいけど)
  • 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使ってる感がモロバレなので、設定をします。

image.png

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

s3がなにもないパスにアクセスしたときに403を返すので、これで代わりにerror用のhtmlを表示させることができます。

動作確認

おつかれさまでした!備忘録的に書いてるので何か漏らしてる気がしますが、
これで問題がなければ 設定したドメインでアクセスしたら s3作成時においた index.htmlが表示でき、APIのパスにアクセスすればAPIの結果が返ってくるようになるはずです。

…もし動かなかったらごめんなさい。 :bow:
ググれば多分解決できる程度に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で基本的なデザイン作成
  • ロジックの分離を意識する

といったぐらいのことしか…やってないかも :thinking:
とりあえず書きます!

create-nuxt-appから少しだけ設定を変える

create-nuxt-appをつくると、ルートにstorepageといったフォルダができます。
ちょっぱやで作る分にはいいのですが、気になる人(自分含む)は
例えばsrcというフォルダ以下に移したい場合はsrcDirというフィールドに設定します。

nuxt.config.ts
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以下に下のように設定を入れました。

nuxt.config.ts
  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は違う気がするな :thinking:

ちなみに テンプレのvueファイルでPostCSSをつかった記述をする場合は下の通りにstyleタグを足します

index.vue
<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とかきいて何かわかりますが、聞き馴染みのない方には最初苦労するかもしれません(主に検索で)
比較的公式のドキュメントもまとまっている……と思います。

ページのコンポーネント構造について

そもそも今回作ったコンポーネントの構造を雑に説明するとこんな感じです。

layouts/default.vue

<template>
  <v-app>
    <nuxt/>
  </v-app>
</template>

上はおまじない的なやつです。vuetifyに欠かせない部分になります。
このテンプレートをベースに、index.vueはつくっています

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
ページ遷移のトランジションが簡単にできちゃうコンポーネント
saaaample.gif


    <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 とは何か? | Vuex

image.png

公式の図からVuexにおけるフロー図です。
このwebページでは、ducksパターンでstoreを作成しています。
React + Reduxでもそうなのですが、action typeactionreducer(Redux) mutation(Vuex)をそれぞれ分けると管理がつらいので、どうせそれぞれが密なものであれば一つのファイルでまとめよう。っていうデザインパターンです。

erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

あとは

  • 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からロジックを剥がすことで
そのコンポーネントの再利用性が高まっていくと思います :thumbsup:

余談ですが、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に足しています。

package.json
    "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に定義したnameappnameの場合

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のボタンを押すとパスを入力する画面が表示されると思います。

image.png

こちらの入力欄に/*と入れてInvalidateを実行すると まるっとキャッシュを消してくれます(実行完了に少しだけ時間がかかります)
注意 やりすぎると課金にかかわるので、やりすぎないかお財布と相談してください :bow:

まとめ

言い訳になりますが、2019/2末ぐらいには開発完了していてそれを出してから放置をしていたため、この備忘録も当時の記憶を頼りに書いてるのでだいぶヌケモレあるんじゃないかな…と思います。
フロントにいたっては、あまり特殊実装もしてないのであとは「コード見て」状態になってますね…/(^o^)\

多分おかしいところあるきがするのでアレば教えてください :bow:

とりあえず整理もせずひたすら書いたのでクソ長いですが、
もしここまで読んでくださった方がいたら本当にありがとうございました!

会社だと整った環境があるので、なかなか1から作ることはないからこそ
お勉強としてやってみましたが…それでもAWSで完結するんだから楽ですよね :innocent:

パーティも終わってこれも書き終わったのでAWSの解約をして
趣味のゲームづくり(Unity)に力を注いでいきたいなという そんな今日このごろ

32
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?