はじめに
FirebaseCloudFunctionsでExpressを用いた際、最初で結構躓いたので記録しておきます。
ただFunctionsにブラウザでアクセスしたらjsonが帰ってくるというものを作ろうとしたところCannot GET /***
と表示されました。そして他のどこにもエラーが出力されないので解決がしづらいものでした。
結論をいうと、これはExpressのエラーでただ純粋に/***
というPathでGETメソッドが見つからない、というだけです。
firebase-functions
は関係なく、ちゃんとExpress呼び出しまで働いてくれてました。
……が、大本の原因はfirebase-functions
に直感的でない挙動があることでした。
ソースコード
PROJECT/functions/src/index.ts
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を使う設定です。
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "/f/**", "function": "main"
},
{
"source": "**",
"destination": "/index.html"
}
]
}
発生したこと
firebase emulators:start
以上のコマンドでローカルで試すと、
http://localhost:5001/PROJECT/us-central1/main/tekito-id
にアクセスするとちゃんと{hoge: "fuga"}
のjsonが帰ってきます。
しかし、
firebase deploy
で本番環境にデプロイしたあと、
https://PROJECT.firebaseapp.com/f/tekito-id
にアクセスするとCannot GET /***
になってしまいます。
何が原因か
expressに以下の関数をミドルウェアとして追加してpathを見てみましょう。
このミドルウェアはすべてのアクセスでログ出力の関数を実行してくれます。
先ほどと同様にブラウザでアクセスしてログを見ます。ローカルではコンソール、本番環境では
Project Console: https://console.firebase.google.com/project/PROJECT/overview
で見れます。
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ファイル)を直します。
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に渡されます。
これにてどちらの環境でも一つのソースコードで動作するようになりました!