Help us understand the problem. What is going on with this article?

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

はじめに

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に渡されます。

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした