LoginSignup
1
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-12

はじめに

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

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1