SPA(Nuxt.js) をFirebase上で動かすにあたって、開発進行中につまずいたこと

# はじめに
最近バックエンドのコードを書く機会が減り、代わりにFirebaseを利用する機会が本当に増えてきました。
本投稿はSPA(Nuxt.js)で実装したものをFirebase上で動かすにあたって、Hello Worldから毛が生えアプリが成長し複雑になっていく過程で、つまずいた体験の紹介になります。

# 1.動的なルーティングが動かなくて、つまずいた
Nuxt.jsでgenerateしてdistディレクトリをFirebase Hostingにdeployすれば、できたできたヤッターとなります。
ですが、動的なルーティング[^1]は単純にそれだけだとFirebase Hosting上では動きません。
とりあえずサクッと対応したいという場合はhashモードにかえましょう。

``` javascript:nuxt.config.js
  router: {
    mode: 'hash',
  },
```

# 2.Functionsで作ったREST APIがローカルから呼べなくて、つまずいた

``` javascript:functions/index.js
const functions = require('firebase-functions');

exports.getSample = functions.https.onRequest(async (request, response) => {
  res.json({success: true, date: (new Date()).getTime() });
});
```
こんな感じで作成したFunctionsをdeployすれば
`https://us-central1-[PROJCECT_ID].cloudfunctions.net/getSample`
というURLのREST APIができます。ブラウザで試しにアクセスすると、正しくJSONが出力されることが確認できました。

それではこれをクライアントから呼び出してみますが、

``` typescript:クライアント呼び出し側（ダメな例）
return axios({
  method: 'GET',
  url: 'https://us-central1-[PROJECT_ID].cloudfunctions.net/getSample',
})
```
こうして、`yarn dev` からの`http://localhost:3000` での動作確認なんてことをしちゃうとエラーになってしまいます。
これは単純なCORSによるエラーなので、対応としては

``` javascript:nuxt.config.js
proxy: {
  '/functions_api': {
    target: 'https://us-central1-[PROJECT_ID].cloudfunctions.net',
    pathRewrite: {
      '^/functions_api': '/',
    },
  },
},
```
``` typescript:クライアント呼び出し側（修正後）
return axios({
  method: 'GET',
  url: '/functions_api/getSample',
})
```
とすれば、`http://localhost:3000` での動作確認もうまくいくようになります。
ちなみに上記`proxy`を変更すれば向け先をEmulatorで動かしているFunctionsにすることも可能ですね。
そんなこんなで開発は進行し、さらに修正して

``` typescript:クライアント呼び出し側（さらに修正後）
const currentUser = firebase.auth().currentUser
if (currentUser) {
  const token = await currentUser.getIdToken()
  return axios({
    method: 'GET',
    url: '/functions_api/getSample',
    headers: { Authorization: `Bearer ${token}` },
  })
}
```
``` javascript:functions/index.js（さらに修正後）
const admin = require('firebase-admin');
const functions = require('firebase-functions');

admin.initializeApp();

exports.getSample = functions.https.onRequest(async (request, response) => {
  const token = req.headers.authorization.split('Bearer ')[1];
  const decodedToken = await admin.auth().verifyIdToken(token);
  
  // decodedToken.uidを用いて適切なアクセス制限実行

  res.json({success: true, date: (new Date()).getTime() });
});
```
Firebase Authenticationを通過しているユーザのみアクセス可能にする、という実装に変えました。

# 3.Functionsで作ったREST APIが本番Hostingから呼べなくて、つまずいた

2の修正後に、generateしたものをFirebase Hostingにdeployして動作確認してみました。

なんということでしょう、またREST APIが呼び出せていません。ブラウザのデバッガーで確認すると

> GET https://[PROJECT_ID].web.app/functions_api/getSample 404

こんなエラーが出ています。え？404？、、あーこのURLにアクセスしちゃうのね。。
ということで、firebase.jsonでrewritesを追記しました。

``` json:firebase.json
"hosting": {
  "public": "dist",
  "ignore": [
    "firebase.json",
    "**/.*",
    "**/node_modules/**"
  ],
  "rewrites": [
    {
      "source": "/functions_api/getSample",
      "function": "getSample"
    },
  },
},
```
これにて、本番Hostingでも正常にアクセスできるようになりました！ヤッター！

# （おまけ）Functionsで作ったREST APIが重くて、つまずいた
せっかく上記2と3を乗り越えて作ったものの、やたらレスポンスが遅いのが気になってしまいました。
重くてもしょうがない性質な処理でもあったので、高速化を試みるのではなく、処理の最初の方で、`res.json()`によりレスポンスだけササッと返して、裏でゆーっくりと処理しておいてくんないかなと思ったものの、上手く行かず。。。
そこで、HTTPリクエストトリガーでの実行をやめ、[Cloud Firestoreトリガー](https://firebase.google.com/docs/functions/firestore-events)に変更して、クライアント側からはFunctionsへのキュー的な指示のためのデータをFirestoreにsetするというやり方に変えてみました。
すると、上記であった「Authenticationを通過しているユーザのみアクセス可能に」を考えなくてすむし、責任分界点がはっきりして開発効率悪くないし、これはこれで案外いいかもな〜って思ったりもしました。

おしまい

ではでは、皆様メリクリ〜

[^1]: `pages`配下に`_id.vue`のようなアンスコ始まりのファイルを設置して、`this.$route.params.id` で値が参照できるNuxtの機能。（参考:[ファイルシステムに基づくルーティング](https://ja.nuxtjs.org/docs/2.x/features/file-system-routing#%E5%8B%95%E7%9A%84%E3%81%AA%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0)）