みんなの「作ってみた」

【ユーザー0人】初心者の大学2年がNuxtとAWSでサーバーレスな小動物経験共有サービスを作った話

2019/09/15

quailDegu
quailDegu

あまりに夏休みが長すぎて暇だったので、何かWebアプリでも作るかと思い立ち「でぐらむ」という小動物系の経験共有サービスを作りました。
JavaScriptの言語仕様も全然わかってないのにVue(Nuxt)を使った超初心者で、色々と戸惑った事もあったのですが良い夏休みになったのでどんな事を考えてどう作ったのかを共有してみます。

作ったサイト


URL: でぐらむ

なんで作ったの?

私自身も「デグー」という齧歯類を飼育しているんですが、生態について調べてあまり情報が出回っておらず個人ブログも散乱しており調べづらかった経験から、そうった事を気軽に投稿できる場所があればかなり便利じゃないかなと思ったからです。
それに投稿して表示させるだけならまあ作れるだろと思った

アーキテクチャ


だいたいこんな感じで、サーバーレスアーキテクチャとなっています。(多分)
CloudFront->サイトのSSL化
S3->サイトの本体
API Gateway&Lambda->投稿・一定時間で更新されるTOP画面用
Cognito->ユーザー認証
DynamoDB->タグとURL,サイトタイトルを記録

作る上で考えたこと

大前提としてユーザーが0人でも金銭的負担が少ない事。
これが保てなければ公開はできません。おそらく単に作るだけならEC2でRailsでも使ってELBでSSL化、RDSで柔軟なデータ操作でいいのですが、そうすると公開しているだけで月40ドルは消えてきます。そんな余裕は貧乏大学生にありません。一か八か予算の範囲でユーザーが増えたら継続・増えなかったら公開終了みたいな事にするよりは、多少低クオリティでも長く公開し、ゆっくり機能を改善してくのが最善だと判断しました

次に考えたとしては「ユーザー登録を必要とするか」です。
これも重要な点で、十分なセキュリティでは無いならユーザー登録系の機能は実装しない方が良いのは間違いありません。
幸いにもこれはAWSのUserPool,Cognitoを使えば最低限のセキュリティは保てそうなのでユーザー登録機能をCognitoを使って実装しました。なにせ月5万MAUまでは無料(一部例外あり)です。はい、想定ユーザー数は0人です。超えません。

Cognitoを使う上で最低限気をつけたこと
CognitoはJWTトークンを使った認証基盤を提供してくれます。
JWTトークンはローカルストレージに保存されるため、XSSを許した場合容易くIDトークンやリフレッシュトークンが盗まれてしまう危険性があります。
そのため、しっかりとユーザーが投稿した内容はサニタイズしてから表示し、もし盗まれたとしてもAPI GatewayのCORS設定を「www.degurum.com」のみ許可する様にして他の場所から投稿する事ができない様にしました。
また、サインアウト処理をすべての発行されたトークンを無効にするglobalSignOut()としました。

他には便利なものはどんどん使っていくというスタンスで色々なライブラリを使いました。特にMarked.jsとsanitize-htmlはとてもサービスとマッチしてて有り難かったです。

Nuxtを選択

タイトルにもありますがNuxtを使って開発しました。静的サイトジェネレーターは色々あってReactとかも魅力的でしたが、学習コストという面ではVue、そしてNuxtは調べた限り群を抜いて秀でてるように感じたためNuxtを選択しました。

投稿・表示機能の実装

記事の投稿機能は投稿内容をS3にJSONで保存する形で実装しました。
表示はS3からJSONをfetchで引っ張ってきて整形させるだけですみ、なにより本文はサイトデザインとは切り離されてるため後からカスタマイズしやすくなっています。
Googleのクローラーがそういったサイトもしっかりクロールしてくれるのかは多少疑問があったのですが、Search Consoleでスクリーンショットを見て見る限りしっかり記事本文が表示されたあとっぽいので問題なさそうです。

ちなみに、このS3にドキュメントを保存するのはDynamoDBのベストプラクティス大きな項目と属性を格納するベストプラクティスを見た感じあながち間違いでもない構成では無いのかなと思ってます。

また、余談ですが記事投稿ページはMarked.jsによるMarkdownのプレビュー機能を付け、_.throttleによるイベントの間引きも行っています。throttleを最初知らなかったんですが、Qiitaのプレビュー機能が少し遅れてる事からなんらかの負荷軽減を置こうなう手段があるのに気が付き調べてたら知ることが出来ました。作ってて楽しかったです。

苦労したこと達

DynamoDBわかんね

RDBMSのSQL文すら良くわかっていないわたしにとって、DynamoDBは本当に良くわからないサービスでした。特に非正規化が推奨される事があるというのはかなり違和感を感じ、どういう構成にするのが良いのかわからなくなってしまいました。
そんな訳でやりたい事とDynamoDBの特性をすり合わせるのに1週間かけました。
結局

Hash Auther Time Tag Title URL
9fd5bc ユーザー名 201909122015 タグ タイトル 9911883024

みたいなパーティションキーにHash,Autherがレンジキーのテーブルに、タグがパーテーションキー,TimeがレンジキーのGSIを作る事でタグと時間での検索ができるようにしました。
タグが複数ある場合はHashキーとTagだけが違う項目がタグの数だけ作成されます。
今でも良い構築だったか自信がありません。推移従属なんて概念は忘れた

CognitoのSDK古いの新しいの

AWSのCognito公式ドキュメントだけでは、レベルの足りない私では実装できません。という事で、手がかりを掴むために様々なWebサイトを参考にさせていただきました。しかし、CognitoのJavaScriptSDKには公開されているのが2種類存在して1つが「amazon-cognito-identity-js」、もう一つが「amplify-js/packages/amazon-cognito-identity-js/」。
前者はアーカイブで、今後は後者を使う必要があります。開発始めた当初は「jsbn.jsとかを読み込んでるサイトと読み込んでないサイトが有るな〜」とか思って暫く悩んでました。

cognitoUser

私の力不足の面もあるのですが、例: JavaScript SDK の使用などを読んでも自分で読み解かないといけない事がいくつかあって大変でした。
その1つにcognitoUserという変数があるのですが

    var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
        ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
    };
    var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
    var userData = {
        Username : 'username',
        Pool : userPool
    };
    var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

で作ったcognitoUserをそのまま

  cognitoUser.authenticateUser(authenticationDetails, {
       onSuccess: function (result) {
           var accessToken = result.getAccessToken().getJwtToken();
           var idToken = result.idToken.jwtToken;
       },
       onFailure: function(err) {
           alert(err);
       },
   });

みたいに使える場合と

    cognitoUser.getSession((err, result) => {
      if (err !== null) {
        //err処理
      }
      AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: IDENTITY_POOL_ID,
        Logins: {
          USER_POOL_ID: result.idToken.jwtToken
        }
      })
    })

で認証?させてから関数に渡さないとエラーを吐く場合があり、使い始めた当初はかなり苦労しました。

苦労しなかったこと

Nuxt意外と書ける

学習コストの低さからVueとNuxtを選んだんですが、それでも思ってたよりは苦労しませんでした。v-modelなどを使ったVueの書き方に最初は馴れなかったものの、開発が進むに連れDocumentインターフェイスはコードから無くなっていきVueの恩恵を徐々に受け取る事ができました。(理解してない事も多いけど今後も学習していきたい)

API関連

API GatewayのLambdaプロキシ統合を使うと、(オーソライザー設定して)ヘッダーにIDトークンを付与してPOSTなどすればユーザーの情報をLambdaに渡してくれるのでLambda側でCognitoの情報を取得する手間が殆どありませんでした。(渡してくれる情報はキャッシュされるっぽいので常に新しい情報が欲しい場合は注意)
LambdaでS3やDynamoDBを操作するのもちゃんとドキュメント読めばわかりました。

GoogleAnalytics

NuxtでGoogleAnalyticsを使う場合はあの「ヘッダーに置いてください」のコードが使えません。なので公式が提供している@nuxtjs/google-analyticsを使う必要があります。
@nuxtjs/google-adsenseもあるぞ。
yarn add してnuxt.config.jsに設定書き込むだけなので一瞬で設定は終わりました。
どうもこれらを使わないとホットリロードの関連で不具合が起きるらしいです。

きれいな アラート

SweetAlertっていう綺麗なアラートを表示するライブラリを使いました。
Progateのアラートもたしか同じ様なデザインだったので、SweetAlertを使ってるのかな

ユーザー0人

で、今の所私以外のユーザーは存在しません。ユーザー登録されてない以上、これは単純にニーズが無いとか人を引きつけるデザインじゃない(脳死でBootstrapを使ってる)、そもそも宣伝をしていない等が原因と思われるので膨大な時間が解決してくれるのを待つか、お金かけて人を呼び込むかくらいしか出来ないかなという感じです。
ユーザーが居ないなら居ないで問題ないというスタンスで作ったのでこういった開発をするという経験自体でプラス収支みたいな所がありますが、人に使ってもらうという貴重な体験につなげていければ最良なので暫くは色々改善していきたいとおもってます。

「もし使われなかったらどうしよう」ではなく「とりあえず作ろう。使われるかどうかはその後の話だ」という気持ちになれるサーバーレスアーキテクチャは趣味で何か公開する事に関して相性のいいものだと感じました。
今後は当サービスの改修以外にも、英語が苦手な私用の、Youtubeに挙げられている動画(CC BY)やSCP FoundationSCP財団のCC BY SAのテキストデータとパブリックドメインな英和辞書データ等を活用してどこからでもアクセスできて無料な英語学習環境をNuxt+PWAで作れたりするのかなとか想像が膨らみます。

ここまで読んでいただきありがとうございました。

特に参考にさせていただいたサイト・記事様

公式

個人