Nuxt + firebaseでサーバレスな図書管理デモアプリを3日で作成・デプロイした


時間でいうと25hで設計・実装・デプロイまでできたのでメモとして残します

完成gif

  • こんな感じで図書を管理する上で必要となるであろう各機能を実装しました。
  • URL載せていたのですがアクセス増えて怖くなったのでURLは消しました。。
  • 執筆する中でバグをチラホラ見つけてしまいましたが、一旦書き切ります・・・

モバイルもホラ(レイアウトは考慮が必要)

経緯(動機)

  • 弊社では業務で使用する図書を経費購入できる制度があることもあり、社内図書がかなり溜まってきていました。
  • 図書の管理は、現状スプレッドシートで管理しており、これが特に破綻していると言うことはなかったのですが、モバイルから触りたいなぁという個人的な都合がありました。
  • そこで簡単な図書管理アプリケーションのデモを作ってみようと思い立ちました。
  • で、自分的に手応えがあったらこれでいきませんかと提案してみよう・・・

技術選定

  • モバイルから触りたい要求を考えるとNuxtのPWA(プログレッシブWebアプリ)モードが一番早そうだと思いました。(モバイルアプリ風にホーム画面にも追加できるし)
  • 結果、Nuxt.js(PWAモード) + firebaseにしました。理由は前述した要件に加えてNuxtに慣れていたことUIコンポーネントが豊富、とかcreate-nuxt-appでセットアップが早いとかありましたが一番はfirebaseの各機能で開発スピードを上げたかったからです(使ったことないけど)。
  • 結果的に3日で動くものができたので選定自体は間違ってなかったと思いました。
  • 最終的には主に以下の技術を使って作成しました

使用技術

  • Nuxt.js:2.11.1
  • TypeScript:3.5.3
  • firebase
    • firebase Authentication
    • Firebase Cloud firestore(DB)
    • Firebase cloud functions
  • ※デプロイはどうしようかと考えましたがNuxt.js を Express のミドルウェアとして動作させてみようと思いfirebase cloud functions でデプロイしてみることにしました。

使用技術を何に使っているのか

  • 画面
    • Nuxt.js を用いて PWAとSSR(サーバサイドレンダリング) を実現
    • TypeScript を用いて静的型付け(オートコンプリート機能他)
  • サーバサイド・DB・認証
    • firebase Cloud functions
    • デプロイに使用
    • firebase Cloud firestore
    • 今回データの永続化の方法としてFirebase Realtime Databaseを使用しています。こちらのデータベースはNoSQL (非リレーショナル) データベースという方式を採用しており、様々なメリデメがありますが、今回のような小規模開発には向いていると思い採用しました。RDBMSとの違いは以下などを参照ください。
    • RDBMS と NoSQL を徹底比較!特徴からそれぞれのメリット・デメリットまで、わかりやすく解説!
    • firebase Authentication
    • 認証

設計

  • とてもシンプルな構成なので、設計は頭のなかでやりました。(通勤時間:30*5=2.5h)
  • 貸出・返却のフローを考慮するとslackに通知送ったりするのも必要?とか考えましたが、デモということで一旦CRUDが確認できる最低限の以下の機能を実装することにしました。

機能(とりあえずCRUDできる最低限)

  • ログイン(かんたんログイン含む)
  • ユーザ作成
  • 図書一覧
    • 文字列検索
  • 貸出・返却申請
  • 新規図書登録

開発工数

  • 設計
    • 通勤電車の中で 2.5h(0.5h * 5 日)
  • 実装
    • 20h
  • テスト・デプロイ(次回はJest使ってE2Eテストやりたい)
    • 機能に対する打鍵テストのみ:1h
    • cloud functions を用いたデプロイ:1.5h

メッセージについて

  • 画面にて表示するメッセージはnuxt-i18nを使用してlocales/各言語.jsonに格納されているファイルを参照して出力するよう工夫しました。
  • 実はこれについては昔Qiitaに執筆していましたNuxt.jsでメッセージの外出し&国際化対応
    国際化

  • 使うときのソースだけ貼ります

hoge.vue

import { i18n } from '~/plugins/nuxt-i18n'

this.message = i18n.tc('error.E-0006')
this.messageFlg = true
  • 引数を入れたい時のメッセージ用に mixin を作成して対応(common-message.ts)
hoge.vue(引数を入れたい場合)
import { i18n } from '~/plugins/nuxt-i18n'
import CommonMessage from '~/mixins/common-message'

this.message = this.editMessage(i18n.tc('info.I-0003'), [
    'ユーザ登録',
    'ログイン'
  ])

デプロイ

  • SSRモードなのでFirebase hostingだけだと動きません。
  • リアルタイムリクエストに対してHTMLを返却するサーバを用意する必要があります。
  • Firebase hosting ではサーバーはたてられないのでcloud functions を追加で使用します
  • cloud functoinsは実行環境としてNodeJSが用意されているためブラウザからのリクエストをcloud functions に集め、そのリクエストに応じたHTML を返却するサー バーを立てることでNuxt でのSSR 対応Web サービスを実現します

フロー

  • 1.ブラウザからのhttpリクエストはすべてcloud functions に集めます。
  • 2.cloud functions 側で動いているサーバーからNuxt によって生成されたHTML を返却します。
  • 3.返却後のHTML のリクエスト(静的ファイル)は、firebase hosting によってhosting しておきます。たとえば、アプリ上で使用している画像やJS ファイルなどです

デプロイ準備としてビルド用のシェルを作り実行

  • 後述のfunctionsの実装で使用するnuxtプロジェクトをビルドするシェルを作成しました。
  • 後々CIに組み込む予定です。
build.sh

# 前回ビルドで作成されたフォルダ群を一度全て削除
rm -rf .nuxt public functions/nuxt

# publicフォルダを作成
mkdir public

# nuxtをbuildする
npm run build

# 各種フォルダのコピー
cp -R .nuxt functions/nuxt
cp -R .nuxt/dist/client public/assets
# MEMO:読み込みたい画像があればapp/staticもコピーする
# cp -R app/static/* public/assets

  • sh build.shで実行します
$ sh build.sh

functions実装

  • functionsの初期化(省略)
  • サーバサイドのコードを書く(これだけTypeScript化できていません。。。)
  • require('nuxt')部分は↑でビルドしたNuxtプロジェクトを読み込んでいます
  • あとはexpress のミドルウェア関数としてnuxt.render を登録することで、nuxt のレンダリングしたhtml を返却するようにしてます
functions/index.js

const functions = require('firebase-functions')
const express = require('express')
const { Nuxt } = require('nuxt') // ビルドしたnuxtプロジェクト

const app = express()
const config = {
 dev: false,
 buildDir: 'nuxt',
 publicPath: '/assets/'
}
const nuxt = new Nuxt(config)
app.use(async (req, res, next) => {
 await nuxt.ready()
 nuxt.render(req, res, next)
})
exports.ssr = functions.https.onRequest(app)

  • firebase deploy --project nuxt-libraryでデプロイ完了です。
$ firebase deploy --project nuxt-library

良かったこと

  • TypeScriptのオートコンプリート(メソッド名とかを補完してくれるやつ)はフロントエンド開発のストレスをかなり軽減してくれました。
  • firebase Authenticationは非常に簡単に認証機能を作れたので採用して良かったです。
  • Nuxtは業務で触っていたので苦労なく実装できましたが、デプロイがとても勉強になりました。

難しかったこと

  • ログイン情報の永続化はベストプラクティスが確立されていないようで迷いました。
  • vuex-persistedstate を使い実現させました。(以下を多分に参考にさせていただきました)
  • nuxt.js を使う時に localStorage で store を永続化する
  • 紆余曲折ありuniversal-cookieを使い cookie に保存することにしました。
    • src\store\authenticate\actions.tsにてログイン時に cookie に保存。
    • src\middleware\auth-cookie.tsにて cookie から取得してます。
  • NoSQLは初めましてでしたが、中・大規模のアプリケーションのメインサーバではなかなか使わないんじゃないかな?と思いました。

その他参考記事