🫵

【最強】firebase functionsのディレクトリ構成はこうしとけ

2024/02/03に公開

対象となる読者

・functionsでhttpリクエストで関数を呼び出している
・バックエンド(functions)のコードが2000行を超え始めた

firebase functionsのファイル分割に悩むすべての人へ

「firebase functions使いやすいけど、ファイル読みづらくなりがちじゃね?」
firebase functionsを一定期間使うと、このような問題にぶち当たるのではないでしょうか。

私もこの問題に悩まされ、「firebase function ファイル分割」とかで検索しました。すると公式サイトが出てきました。
https://firebase.google.com/docs/functions/organize-functions?hl=ja&gen=2nd
「あー、関数ごとにエクスポートしたりとかで対応するのかー。でも今はエクスポートするhttpsは1つしかなくて、その中のコードが多くなってるんだよねー。。」
とこんな感じでした。

具体的に、今回の記事で解決したい問題は以下のようなものです。
firebase functionsだとデフォルトでfunctionsディレクトションの直下にindex.tsが用意されます。なので、何も考えずにコードを書いていくとindex.tsの中にエンドポイントとそのエンドポイントが叩かれた時の処理がまとまったコードが増えていきます。

index.ts
import * as functions from "firebase-functions";
import express = require("express");
const app = express();

app.get("/read", express.json(), async (req: any, res: any) => {
const code1 = () => {
// 省略
}
}

app.post("/post", express.json(), async (req: any, res: any) => {
const code2 = () => {
// 省略
}
}

app.post("/delete", express.json(), async (req: any, res: any) => {
const code3 = () => {
// 省略
}
}

app.post("/update", express.json(), async (req: any, res: any) => {
const code4 = () => {
// 省略
}
}

exports.api = functions
  .https.onRequest(app);

上記のようにすると、"https://{tekitou}/us-central1/api/{エンドポイント}"でAPIを叩けます。例えば"https://{tekitou}/us-central1/api/read"というふうにAPIを叩けます。

エンドポイントや関数の数が上のコードみたいに4個とかなら全然いいのですが、実際にサービスを作っていくとエンドポイントの数が10~30個に膨れ上がることがよくあります。そうなると「あれ、index.ts1つでバックエンド書くのってめっちゃ見辛くね?」となるんですよね。

そうだ、MVC構造とLaravelのディレクトリ構造を適用しよう

はい、結論です。MVCのデザインパターンを参考にして、それをfirebase functionsに適用します。自分はMVCを採用しているPHPのLaravelのディレクトリ構造を参考にしました。今回firebase functionsはバックエンドのAPIサーバーとして使われる想定なのでViewの部分はありません。なので、MとCの部分を意識してファイル分割します。

厳密にMVCのパターンに落とし込むというよりは、使いやすいように関数をcontrollersディレクトリに、ルートをroutesディレクトリにまとめています。また、controllesディレクトリには、DBのデータをいじったり、responseを返す関数を置くため、controllersというディレクトリ名にしました。ちょっと分かりにくかったら各で変えてもらうといいかと!

ディレクトリ構造
- functions
 - src
  - controllers
   - controllers_read.ts
  - routes
   - routes_read.ts
 - index.ts

上記のディレクトリ構造に合わせて、先ほど書いたindex.tsのコードをファイル分割してみましょう。

まず、index.tsは以下のようにします。
index.tsの役割はここでルートごとにルートの行き先を選別することです。例えば"https://{tekitou}/us-central1/read"というURLが叩かれたならreadRouteのファイルに記載されたパスにいきます。

index.ts
import * as functions from 'firebase-functions';
import express = require('express');
import readRoute from './routes/routes_read';
const app = express();
export const api = functions.https.onRequest(app);

app.use('/read', readRoute);

例えば、"https://{tekitou}/us-central1/read"というURIに対してGETメソッドが叩かれたら、code1の関数が実行されます。

routes_read.ts
import express from "express";
const router = express.Router();
import { code1 } from "../controllers/controllers_read"

router.get("/", code1);

export default router;

このように関数はcontrollesディレクトリ内で定義することで、パスと関数の情報を1つのファイルに全部まとめて書くということを回避できます。

controllers_read.ts
export const code1 = async (req : Request, res : Response) => {
// 関数の処理を書く
}

また、上記はREST APIという概念を取り入れながら作られています。エンドポイントは同じであってもHTTPのメソッドがGETかPOSTかで実行する関数を変わります。

例えば、さっきのroutes_read.tsにpostの場合の処理を加えます。

routes_read.ts
import express from "express";
const router = express.Router();
import { code1 } from "../controllers/controllers_read"

router.get("/", code1);
router.post("/", code10);

export default router;

こうすることで、同じ"https://{tekitou}/us-central1/read"というURIであっても、HTTPのメソッドがgetかpostかでcode1が実行されるのか、code10が実行されるのか変えることができます。

最後に

firebase functionsにガッツリコードを書くなら、上記のディレクトリ構造の対策はやっておきましょう!
絶対に3ヶ月後あたりに後悔します!後悔している人が言うのだから間違いないです笑

Discussion