Firebase Advent Calendar 2017 21日目の記事です。
フリーランスでフロントエンドを中心にエンジニアをやっているpotato4dです。
普段はVue.jsを中心に、案件を進めたりコミュニティに関わったりしていますが、今回はそんなVue界隈で今アツいフレームワークであるNuxt.jsとFirebaseを組み合わせて、SPA + SSRにAuthとRTDBを組み合わせたアプリケーションを高速で作る方法を、サンプルとあわせてご紹介します。
今回のサンプル
先にサンプルの完成品から。
Nuxt.js + Firebase + Bulma + Heroku な感じで作っており、Vuex で状態を管理、 vuexfire を使って RTDB と連携し、 Firebase Auth で Google アカウントと認証、 SSR によって投稿は全て適切にレンダリングされ、 UI も Bulma で整え、ホスティングは Heroku で放置というオーバーエンジニアリング構成をしていますので、是非ソースコードを読んでいただけると幸いです。
GitHub(Starしてね): https://github.com/potato4d/nuxt-firebase-sns-example
Demo: https://nuxt-firebase-sns-example.herokuapp.com/
Nuxt.jsとは
Vue.jsを普段使っていないかたですと聞きなれないかと思いますが、Nuxt.jsはVue.js製のフロントエンドフレームワークとなっており、SSR対応をはじめとして、ルーティングの自動生成などを行ってくれる非常に優秀な開発ツールキットとなっています。
Nuxt.jsが提供する機能としては、主に以下のようなものがあります。(公式サイトより引用)
- Vueファイルで記述できること
- コードを自動的に分割すること
- サーバーサイドレンダリング
- 非同期データをハンドリングするパワフルなルーティング
- 静的ファイルの配信
- ES6/ES7のトランスパイレーション
- JSとCSSのバンドル及びミニファイ
- Head要素の管理
- 開発モードにおけるホットリローディング
- SASS, LESS, Stylus などのプリプロセッサのサポート
- HTTP/2 push headers ready
- モジュール構造で拡張できること
Nuxt.js自体の基本については今回は省くこととしますので、気になるかたは手前味噌となりますが、私が以前紹介しているスライド・エントリをご覧いただくと円滑かと思います。
- Vue.js製フレームワークNuxt.jsではじめるUniversalアプリケーション開発
- Nuxt.jsで変わる開発フローとUniversal JavaScriptのイマ #jsfes
Nuxt.jsとFirebaseを組み合わせてできること
そんなNuxt.jsを利用することで、フロントエンドの開発は
- ルーティングを自分で作成する必要がなく
- Vuexのストアはオートローディングされ
- SSRはデフォルトで対応、自由に付け外しができる
という非常に効率が良く、また気軽に実装できる環境が整いましたが、依然としてバックエンドは自分で開発する必要がありました。
また、商用サービスならまだしも、個人サービスでSSRサーバーを建てるNuxtとAPI用のアプリケーションサーバー両方を構築することは、インフラ費も、メンテナンスコストもどうしても増大し、気軽にモノを作ることはできるのはlocalStorageの範囲に限られてしまいます。
そこで今回はその問題を解消するため、Firebaseのリアルタイムデータベース(RTDB)と認証(Auth)を利用して、「ユーザー認証と永続化」の領域をmBaaSに委譲してしまい、Nuxtでアプリケーションをロジックをメインで書いていく構成を実践してみました。
こうすることで、バックエンドはFirebaseのRTDBのRuleの記述とAuthも初期設定のみで済んでしまいますので、非常に簡単に構築でき、フロントエンドはVueの強力なサポートのもと、Nuxtを利用することで高い効率で開発が可能となります。
実際、今回のサンプルについては、データの操作周り(Store)のコードは全て含めても80行程度となっており、効率よく実装できているさまがわかるかと思います。
今回の記事は、そんな Nuxt.js + Firebase の組み合わせにおいて、それぞれを少し触ったことがあるかたを対象に、開発におけるコツをご紹介いたします。
Nuxt.js と Firebase での開発のコツ
ここからは実際の開発においてのハマリポイントやオススメのソリューションをご紹介します。
プロジェクトの初期化
Nuxt.jsのプロジェクトを作成する時は、Vue公式のスキャフォールディングツールである vue-cli
を利用することをおすすめします。
グローバル環境の汚染を特に気にしないかたは、以下のようにグローバルに入れておくと便利でしょう。
$ yarn add global vue-cli
Nuxt.jsは、GitHubの nuxt-community Orgnization にて、複数のボイラープレートを公開しています。
作る時点で必要なものが全て見えているわけではない状況では、 starter-template を利用することをおすすめします。
starter-templateは、旧名が nuxt/starter
であり、リダイレクトされますので、こちらで覚えておいて vue init
すると手っ取り早く作成できます。
$ vue init nuxt/starter
アプリケーションのデータの永続化
アプリケーションのデータの永続化は Firebase のリアルタイムデータベース(RTDB)を利用すると良いでしょう。
Firebase の RTDB は基本的に無料で提供されており、また、リアルタイムなお送受信が可能であること、サーバーの管理の必要が無いこと、 MongoDB などの NoSQL 型データベースで一般的な KVS 形式の JSON ストアを提供しています。
Firebase の RTDB は、 JSON 形式のデータを簡単に取り扱うという点では便利ですが、 RTDB の機能を存分に活用したい、リアルタイムな送受信を行いたいというモチベーションが生まれた時に少し問題があります。
RTDB は WebSocket ベースであり、一度参照したデータ構造に対して、独自のイベントシステムのもとデータが送られてきます。
これは、JavaScriptの普遍的なイベント駆動を踏襲している Vue においては、気軽に使うには状態を管理しづらいところがありますので、 Vue.js 謹製の vuefire あるいは vuefire の開発者による vuexfire を利用するのが良いでしょう。
Nuxtの場合は、Vuexストアが非常に扱いやすくなるように作られていますので、基本的には vuexfire の利用をおすすめします。
vuexfire は非常にシンプルであり、 Firebase の RTDB の領域のバインディングのみを行ってくれます。
シンプルな例ですと、以下のように記述することで、 RTDB の /posts/
の中身の切り替えをリアルタイムで Vuex Store 内の state.posts に反映してくれます。
受信だけを考える場合は、これ以上特に追加の設定を必要とせず、データの挿入/編集/削除全てに対してリアルタイムで追従してくれます。
import Vue from 'vue'
import Vuex from 'vuex'
import firebase from 'firebase'
import { firebaseMutations, firebaseAction } from 'vuexfire'
const db = firebase.database()
const postsRef = db.ref('/posts')
Vue.use(Vuex)
return new Vuex.Store({
state: {
posts: []
},
mutations: {
...firebaseMutations
},
actions: {
INIT_POSTS: firebaseAction(({ bindFirebaseRef }) => {
bindFirebaseRef('posts', postsRef)
})
}
}
Oauth認証システムと認証情報の永続化
Web アプリケーションを開発するとなると、大抵の場合認証は必須となります。この認証分野においても、 Firebaseを利用すると良いでしょう。
Firebase の RTDB の他に便利だとよく挙げられる大きなサービスとして、 Auth サービスがあります。
これは、 Twitter, Facebook, GitHub, Google などのサービスの OAuth を一元管理した上で、認証情報の永続化やコールバックの取り扱いも全て受け持ってくれるすぐれものです。フロントエンド中心であれば、是非利用したいサービスであることは間違いありません。
しかしながら、vuefire や vuexfire は Firebase の RTDB 以外の領域については全くのノータッチとなりますので、工夫が必要となります。
Auth を利用したい場合については Firebase の機能を直で叩いてやって、その上でそれをラップしてしまうというのが現状一番の解決法でしょう。
今回のサンプルの場合、このような形で Firebase のラッパーと Auth のラッパーを別に作成しました。
基本的に Vue.js 単体でも Nuxt.js の利用でも HMR が存在するかと思いますが、 Firebase はそこそこレガシーであるため、愚直に HMR を行うと動作しません。
対処法としては、 if (!firebase.apps.length)
の行を追加した Firebase のラッパーを追加してやると動作するようになります。
是非追加しておきましょう。また、通常の import の代わりにこのラッパーを読み出すことを忘れずに。
import firebase from 'firebase'
if (!firebase.apps.length) {
firebase.initializeApp(
{
apiKey: process.env.APIKEY,
authDomain: process.env.AUTHDOMAIN,
databaseURL: process.env.DATABASEURL,
projectId: process.env.PROJECTID,
storageBucket: process.env.STORAGEBUCKET,
messagingSenderId: process.env.MESSAGINGSENDERID
}
)
}
export default firebase
また、 Auth についても、 firebase.auth().onAuthStateChanged()
というメソッドが認証情報の取得および変更検知を担いますが、これはコールバックがデフォルトとなっていますので、 util.promisify
を利用するか、以下のように Promise ラッパーを自作すると、メインで記述するコードでは
Promise として扱うことができ、 async/await で呼び出せるため便利です。
import firebase from '~/plugins/firebase'
function auth () {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
resolve(user || false)
})
})
}
export default auth
迷ったら認証はGoogleアカウントで
余談ですが、迷ったら認証はGoogleアカウント認証を利用することをおすすめします。
メール認証は煩雑ですし、 Twitter/Facebook/GitHub 認証には専用のAPIトークンを作成する必要があります。
特に Twitter については、 OAuth アプリケーションの締め出しが厳しくなっていますので、控えたほうが良いのは勿論、ほかのサービスについてもそれなりに取得に手間がかかるため、ワンクリックで有効化できる Google 認証を使っておくと良いでしょう。
何より Firebase は Google 製ですので、優先して使っておくとなんらかの恩恵があるかもしれません。
ちなみに、中々探しても出てこない Firebase の認証ですが、以下の3行でシンプルに OAuth を行うことができます。
import firebase from 'firebase'
const provider = new firebase.auth.GoogleAuthProvider()
firebase.auth().signInWithRedirect(provider)
リアルタイムなコンテンツと SSR
リアルタイムなコンテンツと SSR の併用となるとなかなか難しいところがありますが、ある程度の割り切り方をして進めるのが一番幸せな方法でしょう。
今回は、リアルタイム性の高いコンテンツであるトップのタイムラインついては、SSRのメリットが特に無いため、はじめのレンダリング時は空になるようにしました。
逆に、単一の投稿ページにおいては、 Firebase RTDB の機能の一つ、一度だけ取得し、リアルタイムな変更は行わない .once
メソッドを利用して取得することで、 SSR を可能としています。
この切り分けかたによって、 SSR がほぼ必要ない vuexfire 層と、必要となる Firebase RTDB once 層に分けることができます。
勿論、 .once
を利用すると RTDB の恩恵のリアルタイムな部分はまったく受けられなくなりますが、逆に言えばアクセス後に投稿が削除される。といったおかしな挙動のもとにもならないため安心できますし、余計な懸念点を増やさないという意味では、自身のアプリケーションの SSR 戦略を見直す上では非常に良い選択肢であるといえるでしょう。
どうしても SSR をした上でリアルタイムなコンテンツを取得したいという場合は、 Vuex の初期ステートに .once
結果を代入後、 vuexfire と連携する。というアプローチを取ることをおすすめします。
SSR しない選択肢とリアルタイムではないデータベースの取り扱い
ここまでは主に Nuxt.js の SSR の側面と Firebase のリアルタイムなデータベースを中心に紹介しましたが、これらを取り扱うにあたって覚えておくと良いことがあります。
無理にSSRしない
また、 Nuxt.js を利用しているからとといって、無理に SSR をする必要もありません。
Nuxt.js には、 mode: 'spa'
による SPA 機能、そして、静的サイト生成のための generate
機能がありますので、 SSR をしないといけない状況では無ければ、静的サイトとして最終的に書き出してしまうと、 AWS S3, GitHub Pages, Firebase Hosting, Netlify などの静的サイトホスティングサービスで、無料 or 格安でホスティングができるので、 「SSRしない」 という選択肢も常に考えておきましょう。
SSR はコストのかかる行為です。
そして、 Nuxt.js は、 SSRだけではなく、 prefetch や Vuex ストアのオートローディング、 SPA のルーティングの自動生成など、 SSR を利用せずとも有効活用できるシステムが豊富に揃っています。
目を引く機能ではありますが、 SSR に囚われすぎないことは重要なポイントの一つとなります。
nuxt.config.js
にこの一行を追加するだけで SSR をやめて SPA モードになります
無理にリアルタイムなデータの取扱をしない
また、 Firebase の RTDB についても同様です。
確かにリアルタイムな NoSQL のデータベースと聞くと、使ってみたくはなりますが、その一方で、多くの場合リアルタイム通信というものは非常に考えることが多くなる概念です。
Firebase については、先程ご紹介した .once()
をはじめとして、 RTDB を 認証付きの CRUD 可能な Web API のように利用することができる側面もあります。むしろ、一般的な Web アプリケーションを構築したい場合は殆どこちらでこと足りるでしょう。
Nuxt.js の SSR と同じくらいインパクトがありますが、同じくらい必要であるケースが限られる技術ではありますので、こちらも 「リアルタイムには通信しない」 という選択肢も常に考えておきましょう。
おわりに
最近は Nuxt.js で開発を行う機会が非常に多く、その強力な機能を Firebase と組み合わせることでより効率的な開発ができるかと思い、ご紹介いたしました。
簡単な CRUD であれば、もはや SPA + Firebase で作ってしまえるような時代はきていますので、その恩恵を存分に享受しながら、効率よくアプリケーションを開発していきたいところです。
Twitterなどでは「Railsみたいで明確」という声も上がるほど、非フロントエンドのかたにもわかりやすいフレームワークである Nuxt.js を、無料ではじめられてかつ高機能、 Web アプリケーションにも使える優良な mBaaS サービス Firebase と連携することで、より便利に使っていきましょう。
ちなみにですが、このサンプルを公開するために Firebase のプロジェクトを生やそうとしたところ、上限の制限に引っかかり、 Google にプロジェクト数の上限引き上げを申請、調子に乗って「50プロジェクトまで引き上げてくれ!」と申請を送ったところ、 Google に 「そんなに使いたいなら$50を先払いしてくれ」 と言われて 50ドル 貢ぐ事となりましたので、皆さん Firebase の乱用には気をつけましょう。