ロゴテキスト ロゴ

    NxでFirebase Functionsのアプリケーションを作成する その1

    NxでFirebase Functionsのアプリケーションを作成する その1

    Nxを使っているプロジェクトでFirebase Functionsを使ってバックエンドを構築する事になりました。



    NxにはFirebase Functionsを簡単に作成するためのテンプレートが用意されていないため、

    自分で設定する必要があります。


    この記事ではNxを使ってFirebase Functionsのアプリケーションを設定する方法を記載します。





    ちなみに、理解するのが難しかったため細かめに記載してます…!!

    (ドキュメントあんまりないし難しいし、半べそかきそうになってました😂)



    この記事でFirebaseへのデプロイまで記載しようとしたのですが、

    長くなるため前後編になっております。



    後編はこちら

    検証した環境

    1 nx 14.6.3
    2 firebase-admin 11.0.1
    3 firebase-functions 3.23.0


    Nxで実行するための下準備

    firebase init 実行

    monorepoのrootでfirebase init

    $ firebase init


    • ESLintはNxのものがあるため nを選択
    • install dependenciesは後ほどNxワークスペース機能を利用するため nを選択
    firebase initした時の設定

    ここまでで以下のようなファイルが作成されます

    .
    ├── .firebaserc
    ├── firebase.json
    ├── functions
    │   ├── package.json
    │   ├── src
    │   │   └── index.ts
    │   └── tsconfig.json


    Nxのアプリケーションを作成

    Nxにはfirebaseを作るようなテンプレートの用意がないため、

    かわりにNxでnodeのアプリケーションを作成出来るようにします。

    $ yarn add -D @nrwl/node



    Nxでnodeのアプリケーションを作成します。


    作成時に名前を聞かれるので私は backend としました

    $ npx nx generate @nrwl/node:application
     
    >  NX  Generating @nrwl/node:application
     
    ? What name would you like to use for the node application? ›
    ・・・

    apps/backend/ 内に様々なファイルが作成されます



    functionsを実行するためのファイルを用意

    Nxのコマンドを使ってfirebase functionsの処理を行いたいので、

    最終的に実行される apps/backend/src/main.ts を編集します

    apps/backend/src/main.ts
    import * as functions from 'firebase-functions'
     
    // // Start writing Firebase Functions
    // // https://firebase.google.com/docs/functions/typescript
    //
    export const helloWorld = functions.https.onRequest((request, response) => {
      functions.logger.info('Hello logs!', { structuredData: true })
      response.send('Hello from Firebase!')
    })

    ↑は firebase initで functions/src/index.ts に書き出された内容をまるっとコピーしたものです



    firebase関係のパッケージを追加

    下準備の最後に firebase init 時に install dependenciesでnを選択したため、

    firebase関連のパッケージが入っていないのでインストールします。

    $ yarn add firebase-admin firebase-functions



    Nxでfirebaseの処理を実行出来るようにする

    ここからいよいよNxのアプリケーションでFirebase Functionsを実行出来るようにしていきます!

    前提

    Firebase Functionsを実行する上で確実に必要なnpm-scriptsは以下の2つ


    • 開発時に必要
      • serve
    • デプロイ時に必要
      • deploy
    functions/package.json
    {
      "scripts": {
        "build": "tsc",
        "build:watch": "tsc --watch",
        "serve": "npm run build && firebase emulators:start --only functions",
        "shell": "npm run build && firebase functions:shell",
        "start": "npm run shell",
        "deploy": "firebase deploy --only functions",
        "logs": "firebase functions:log"
      },
      "engines": {
        "node": "16"
      },
      "main": "lib/index.js",
      ・・・
    }

    まずserveコマンドを使えるようにします。

    (この記事では長くなるのでserveコマンドのみの紹介です)



    serveを実行出来るようにする

    最終的にnx run backend:serveコマンドを実行した際にFirebase Functionsのサーバがローカルで立ち上がるようにします。

    最終的に nx run backend:serve でFirebase Functionsのサーバがローカルで立ち上がるようにする



    npm-scriptsのserveについて

    Firebase Functionsが自動で作成したnpm-scriptsのserveコマンドを見てみると

    functions/package.json
    "scripts": {
      ・・・
      "build": "tsc",
      "serve": "npm run build && firebase emulators:start --only functions",
      ・・・
    1. tscでビルドする(デフォルトだと functions/lib/ 配下に書き出される)
    2. firebase emulators:start --only functions を実行する

    という流れになっているのが分かります。



    nxで再現する

    nx run backend:serveで上記の流れを再現していきます。



    nxのコマンドは、それぞれのアプリケーション内のproject.jsonファイルで管理します

    backend/project.json
    {
      ・・・
      "targets": {
        "build": {
          "executor": "@nrwl/node:webpack",
          "outputs": ["{options.outputPath}"],
          "options": {
            "outputPath": "dist/apps/backend",
          ・・・
        },
        "serve": {
          "executor": "@nrwl/node:node",
          "options": {
            "buildTarget": "backend:build"
          },
          ・・・

    targets内のキー名がコマンド名になるため、

    buildコマンド・serveコマンドが用意されている事が分かります!




    試しにnpx nx run backend:buildを実行すると

    • apps/backend/src/main.ts → dist/apps/backend/main.js

    に変換してくれる(ビルドしてくれる)事を確認出来ます。



    つまり

    • npx nx run backend:buildを実行(dist/apps/backend/main.js を作成)
    • firebase emulators:start --only functionsでビルドされたmain.jsを読み込む

    という流れが組めれば、

    Firebase Functionsをローカルで実行出来そうです。




    project.jsonを編集します

    backend/project.json
    {
      ・・・
      "targets": {
        "build": {
          ・・・
        },
        "serve": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "commands": [
              {
                "command":"nx run backend:build"
              },
              {
                "command":"firebase emulators:start --only functions --inspect-functions"
              }
            ],
            "parallel": false
          }
        },

    この記述は下記と同じ意味合いになります

    $ nx run backend:build && firebase emulators:start --only functions --inspect-functions



    これでnx run backend:serveを実行してみます。

    $ npx nx run backend:serve
    > nx run backend:serve
    ・・・
    i  functions: Watching "/Users/yuu/src/github.com/yuki-takara/<project-name>/functions" for Cloud Functions...
    i  emulators: Shutting down emulators.
    ・・・
    エミュレータが監視しているフォルダが \<プロジェクトroot\>/functions/ になっている

    私の環境だとまずfunctionsのエミュレータ起動で失敗しますが、


    それ以上に重要なのが上記画像の赤枠で囲った箇所の通り

    Firebase Functionsのエミュレータが監視しているフォルダが 「<プロジェクトroot>/functions/」 という事ですね。


    今回はビルド先が「<プロジェクトroot>/dist/apps/backend/」なのでここを読み込むようにします。





    functionsの読み込み先を変更する

    functionsの読み込み先変更はシンプルで、

    firebase init時に作成された 「firebase.json」のsourceを指定する事で実現します。

    firebase.json
    {
      "functions": {
        "source": "dist/apps/backend"
      }
    }

    sourceを設定する際、

    firebase.jsonに最初から記述されていた predeploy も削除して問題なしです。



    ただし、ここまでだとまだエミュレータの起動が出来ません。

    sourceで読み込み先フォルダを変更しても起動に失敗。。

    先ほどまでとは違うエラーメッセージなので、

    挙動が変わったのは分かりつつも--verboseしても何しても詳細が分からない。。😭



    ここでかなりつまったものの、最終的に 「package.json」が足りない、という事が分かりました!





    package.jsonを動的に作成する

    ごにょごにょやっているうちに

    firebase emulators:start --only functions は対象ディレクトリのpackage.jsonのmainに設定したjsファイルを実行している事が分かりました!


    firebase.json
    {
      "functions": {
        "source": "dist/apps/backend"
      }
    }
    dist/apps/backend/package.json
    {
      ・・・
      "main": "main.js",
      ・・・

    と記載すればエミュレータが「dist/apps/backend/main.js」を参照してくれる、という事です。





    dist/apps/backend/にpackage.jsonを用意するために、

    以下の記事を参考にscriptファイルを用意します。

    【Nx+Angular+NestJS+Firebase】15分でフルスタックPWA環境を構築しデプロイする - Qiita



    scriptファイルの作成先はNxが最初から用意してくれる tools フォルダに作成していきます


    tools/scripts/generate-api-package-json.ts
    import { readFileSync, writeFileSync } from 'fs'
    import { join } from 'path'
    import * as fs from 'fs'
     
    async function main() {
      const BASE_PATH = join(__dirname, '..', '..')
      const srcDir = BASE_PATH
      const distDir = join(BASE_PATH, 'dist/apps/backend')
     
      const src = readFileSync(join(srcDir, 'package.json')).toString()
      const dist = convertPackageJson(src)
     
      if (!fs.existsSync(distDir)) fs.mkdirSync(distDir, { recursive: true })
      writeFileSync(join(distDir, 'package.json'), dist)
    }
     
    function convertPackageJson(src: string): string {
      const packageJson = JSON.parse(src)
      delete packageJson['scripts']
      delete packageJson['devDependencies']
      packageJson['main'] = 'main.js'
      packageJson['engines'] = { node: '16' }
      return JSON.stringify(packageJson, null, '\t')
    }
     
    main().then()

    このscriptは以下の事を実行します

    • ./package.json を元に dist/apps/backend/package.json を作成する
    • その際に元となるpackage.jsonで不要な項目は削除、必要な項目を改めて追加


    後はこれをnx run backend:serveで実行すればよいのでproject.jsonを編集します

    backend/project.json
    {
      ・・・
      "targets": {
        "build": {
          ・・・
        },
        "serve": {
          "builder": "@nrwl/workspace:run-commands",
          "options": {
            "commands": [
              {
                "command":"nx run backend:build"
              },
              {
                "command":"ts-node ./tools/scripts/generate-api-package-json.ts"
              },
              {
                "command":"firebase emulators:start --only functions --inspect-functions"
              }
            ],
            "parallel": false
          }
        },
    1. ビルドする(dist/apps/backend/ 作成)
    2. dist/apps/backend/package.json 作成
    3. functions起動

    の流れになります。



    これでnx run backend:serveを実行すると

    nx run backend:serve でFirebase Functionsのサーバがローカルで立ち上がるように!

    Firebase Functionsが起動出来ました!



    そして赤枠で囲ったURLにアクセスしてみると

    apps/backend/src/main.ts に記載した内容がブラウザで表示された

    無事 apps/backend/src/main.ts に記載した内容が表示されています。


    これで開発が進められますね☺️





    Firebase Functionsにデプロイする

    記事が長くなったので次の記事で記載していきます!

    プロフィールの背景画像 プロフィール画像
    Yuki Takara
    都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。