React x Firebase を用いた Web アプリケーション開発時のハマりどころと解決策

## 概要

これから React x Firebase の構成で Web アプリケーションを開発する方が同じところでハマらないようにハマりどころと解決策をまとめました。

この資料は [Firebase Meetup #15 Cloud Functions Day](https://qiita.com/tanabee/items/fceca6e80969498b54d4) の LT 資料です。

スライドは以下です。
[React x Firebase を用いた Web アプリケーション開発時のハマりどころと解決策 - Speaker Deck](https://speakerdeck.com/tanabee/react-x-firebase-tips)

## 1. Hosting と同じドメインで Cloud Functions の関数を呼び出す

Cloud Functions のデフォルトで生成される URL は以下ですが、

`https://[region]-[project id].cloudfunctions.net/[function name]`

以下の URL で呼び出したいことがあります。

* `https://[project id].web.app/[function name]`
* `https://[project id].firebaseapp.com/[function name]`
* `https://[独自ドメイン]/[function name]`

Hosting の設定ファイルである firebase.json の rewrites の設定をすれば OK です。以下の設定で /notifications にアクセスされた時に Cloud Functions の notifications 関数を実行し、その他のパスにアクセスされた場合は index.html が参照されます。

```JSON:firebase.json
  "hosting": {
    "rewrites": [
      {
        "source": "/notifications",
        "function": "notifications"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
```

Functions は以下のように、通常通り実装すれば良いです。

```JavaScript:functions/index.js
exports.notifications = functions
  .https
  .onRequest((req, res) => {
    // ...
  };
```

## 2. ローカル環境の React から Emulator の Functions を呼び出す

やりたかったことは以下です。

* Functions をいちいちデプロイして検証したくない
* React のホットリロードはそのまま使いたい

そのため、 React は yarn start して Functions は emulator を使って接続します。 Functions をローカルで動作させるために [Emulator Suite](https://firebase.google.com/docs/emulator-suite/connect_and_prototype?database=Firestore) を使います。

```sh:Command
$ firebase emulators:start --only functions
```

```JavaScript:src/index.js
const app = firebase.initializeApp(config)

if (process.env.NODE_ENV === 'development') {
  app.functions().useFunctionsEmulator('http://localhost:5001')
}
```

## 3. React と Functions で Firestore の参照先を合わせる

2 の設定を行った場合、 Firestore を利用していると以下のような状態になります。

* React: Cloud の Firestore を参照
* Functions: Emulator の Firestore 参照

そのため、Cloud か Emulator か参照先を統一する必要があります。

### 3-1. Cloud の Firestore を参照する

以下のような構成です。

* React: yarn start
* Functions: Emulator
* Firestore: Cloud

この場合のメリデメは以下のようになります。

* メリット: 開発時に Firebase console からデータを確認できる
* デメリット: Cloud のデータが更新されるため、複数人開発には不向き。リリース後は [Firebase を複数プロジェクト構成](https://firebase.google.com/docs/cli?hl=ja#project_aliases) にするなどの工夫が必要

設定は以下のように行います。Functions のディレクトリで @google-cloud/firestore パッケージをインストールして、 functions/index.js からそのパッケージを利用します。 引数には Firebase の project id を指定します。

```sh:Command
$ npm install --save @google-cloud/firestore
```

```JavaScript:functions/index.js
const Firestore = require('@google-cloud/firestore')
const admin = require('firebase-admin')

const db = (() => {
  if (process.env.FUNCTIONS_EMULATOR) {
    return new Firestore({ projectId: 'your project id' })
  } else {
    return admin.firestore()
  }
})()
```

公式ドキュメントの [認証の開始](https://cloud.google.com/docs/authentication/getting-started?hl=ja) ページからサービスアカウントキーを作成してローカルに配置し、 .bashrc や .zshrc などに追記しておきます。

```sh:Command
$ export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"
```

### 3-2. ローカルの Firestore を参照する

以下のような構成です。

* React: yarn start
* Functions: Emulator
* Firestore: Emulator

この場合のメリデメは以下のようになります。

* メリット: ローカル環境に閉じてデータを変更できるので複数人開発やリリース後の運用にも向いている
* デメリット: データの確認が難しい

デメリットとしてデータ確認が難しいとしましたが、これは Firestore CLI などがない（？）ためで、もし良いアクセス方法ご存じの方いたらコメントください。

設定は以下のように行います。[公式ドキュメント](https://firebase.google.com/docs/emulator-suite/connect_and_prototype?database=Firestore#instrument_your_app_to_talk_to_the_emulators) でもこの方法が紹介されています。

```sh:Command
$ firebase emulators:start --only functions,firestore
```

```JavaScript:src/index.js
const app = firebase.initializeApp(config)

if (process.env.NODE_ENV === 'development') {
  app.functions().useFunctionsEmulator('http://localhost:5001')
  db.settings({
    host: "localhost:8081",
    ssl: false
  });
}
```

## 4. Hosting から asia-northeast1 region の Functions を呼び出せない

以下のように region 指定をすると Hosting から呼び出せなくなる

```JavaScript:functions/index.js
exports.getEvents = functions
  .region(‘asia-northeast1’)
  .https()
  .onCall((data) => {
    // ...
  });
```

![スクリーンショット 2019-12-02 14.49.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19698/821282bb-4015-bf08-57af-f8bb1be60c64.png)


[公式ドキュメント](https://firebase.google.com/docs/hosting/functions?hl=ja) を見ると

> Firebase Hosting は、us-central1 でのみ Cloud Functions をサポートします。

との記述が。。というわけで、Hosting とつなぐ Function は、現状 region 指定しないで運用するしかなさそうです。

以上！