FirebaseCloudFunctionsでExpressを使うと"Cannot GET /***"が出る

## はじめに
FirebaseCloudFunctionsでExpressを用いた際、最初で結構躓いたので記録しておきます。
ただFunctionsにブラウザでアクセスしたらjsonが帰ってくるというものを作ろうとしたところ`Cannot GET /***`と表示されました。そして他のどこにもエラーが出力されないので解決がしづらいものでした。

結論をいうと、これはExpressのエラーでただ純粋に`/***`というPathでGETメソッドが見つからない、というだけです。
`firebase-functions`は関係なく、ちゃんとExpress呼び出しまで働いてくれてました。
……が、大本の原因は`firebase-functions`に直感的でない挙動があることでした。

## ソースコード

`PROJECT/functions/src/index.ts`

```typescript
import {NextFunction, Request, Response} from 'express'
const express = require('express');
const functions = require('firebase-functions');

const app = express();

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();

// build multiple CRUD interfaces:
app.get('/:id', (req: Request, res: Response) => {
    return res.send({hoge: "fuga"})
});

// Expose Express API as a single Cloud Function:
exports.main = functions.https.onRequest(app);
```

`firabase.json`特定のURLだけFunctionを使う設定です。

```json
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/f/**", "function": "main"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
```

## 発生したこと
```bash
firebase emulators:start
```

以上のコマンドでローカルで試すと、
`http://localhost:5001/PROJECT/us-central1/main/tekito-id`にアクセスするとちゃんと`{hoge: "fuga"}`のjsonが帰ってきます。
しかし、

```bash
firebase deploy
```

で本番環境にデプロイしたあと、
`https://PROJECT.firebaseapp.com/f/tekito-id`にアクセスすると`Cannot GET /***`になってしまいます。

## 何が原因か

expressに以下の関数をミドルウェアとして追加してpathを見てみましょう。
このミドルウェアはすべてのアクセスでログ出力の関数を実行してくれます。

先ほどと同様にブラウザでアクセスしてログを見ます。ローカルではコンソール、本番環境では
`Project Console: https://console.firebase.google.com/project/PROJECT/overview`
で見れます。

```typescript
import {NextFunction, Request, Response} from 'express'
const express = require('express');
const functions = require('firebase-functions');

const app = express();

const admin = require('firebase-admin');
admin.initializeApp();

// ここを追加！！！！！！！！！！！！！！
app.use(function (req: Request, res: Response, next: NextFunction) {
    console.log(req.path);
    next()
});

app.get('/:id', (req: Request, res: Response) => {
    return res.send({hoge: "fuga"})
});

exports.main = functions.https.onRequest(app);
```

```
console.log()
// ローカル
/tekito-id

// 本番
/f/tekito-id
```

**なんで違うんや！**
そうなんです。
本番になると渡されるpathが変わります。

## 対処
まず本番と環境の違いを出すために以下のコマンドでFirebaseの環境変数を設定します。
`firebase functions:config:set environment.production=true`

ここでtrue自体は文字列になってしまいますし大した意味はありません。ただ、 `production` という中身がtruthyなキーを持っていることが重要です。

次にFunction（TSファイル）を直します。

```typescript
import {NextFunction, Request, Response} from 'express'
const express = require('express');
const functions = require('firebase-functions');

const app = express();
const router = express.Router();// 追加

const admin = require('firebase-admin');
admin.initializeApp();

// 条件分岐
if (functions.config().environment.production) {
    app.use('/f', router);
} else {
    app.use('', router);
}

// 変更
router.get('/:id', (req: Request, res: Response) => {
    return res.send({hoge: "fuga"})
});

exports.main = functions.https.onRequest(app);
```

ここでレスポンスを返すメインの関数をrouterに変更します。
このexpress.Router()クラスはミニアプリケーションと呼ばれ、Pathごとに処理をまとめたり、moduleに切り分けたりする際に使います。

条件文において本番では'true'の文字列が入るため `/f` 以降のPathをrouterに渡します。
ローカル環境ではundefinedになるため何もつけないPathがrouterに渡されます。

これにてどちらの環境でも一つのソースコードで動作するようになりました！