Appearance
與 Firebase 的前緣
以前曾經待過一家超愛使用 Firebase 作為後端的接案公司工作,當時是我第一次接觸到 Firebase,Firebase 底下有許多產品,如:具有運算功能的 functions、資料庫的 RealTime Database、檔案存儲的 Cloud Storage...。 印象深刻的就是 RealTime Database 它是一種 NoSQL 資料庫,無須事先定義 DB schema,彈性是很高,但是應用在當時儲存需求為結構嚴謹的資料上,顯然就有許多問題,像是在合作開發下的應用,每個人的程式都對資料庫的文件補刀,導致同一類型的資料結構不一致。
以為使用 Firebase 快速方便能夠節省時間成本,結果反而花了更多時間在處理程式的例外錯誤,當時算是被迫使用錯誤的解決方案,雖然不是 Firebase 的錯,但從此讓我對 Firebase 有著負面印象,畢竟是不好的回憶阿。 但必須說在當時前端能夠即時響應資料變化的功能真的很潮。
與 Firebase 的再續前緣
最近工作上的需求讓我想到 Firebase 的用武之地,整個網站是靜態的,只有少數幾個特例是要能夠即時反映資料更新於網頁上,資料都是單例,不會因一般使用者操作有多例資料的產生,僅會隨一般使用者的操作而有資料的更新。
舉例來說,像是一個 counter 計數器,每個使用者都可以對這個計數器進行 +1 的操作,大家看到的都是同一組數字。 這樣的需求需要後端的計算及儲存功能,但如果要為了這樣簡單的需求開一個網站框架的專案,架設一個後端伺服器或將應用跑在現有 K8S 叢集上,實在是太浪費資源了。
此時 Firebase 就派上用場了:
- Functions:使用它的計算功能可以讓我們建立一個 API 來執行 +1 的操作,提供給前端調用。
- Realtime Database:使用它的資料庫功能來儲存計數器的數字,同時基於它的 Realtime 功能,前端可以在不重整網頁的情況下即時看到數字的變化。
已經有好幾年沒有接觸 Firebase,隨著它的改版有些使用跟當時不太一樣,所以記錄這次的使用。
建立 Firebase 專案
sh
# 安裝 Firebase CLI
npm install -g firebase-tools
# 建立專案資料夾
mkdir counter && cd $_
# 初始化 Firebase 專案
# 第一次使用會需要登入 Google 帳號,登入後會提示選擇專案現有或建立新專案
# 選擇要使用的功能:Functions 及 Realtime Database
firebase init
# 為了在本地端測試,需要安裝 Firebase Emulator
# 選擇要安裝的模擬器:Functions 及 Realtime Database
# 如果沒有安裝 Java,會提示安裝,安裝完後再執行一次 firebase init emulators
firebase init emulators
檔案結構
初始化完專案後,得到的檔案結構如下:
├── .firebaserc
├── .gitignore
├── database.rules.json
├── firebase.json
└── functions
├── .gitignore
├── index.js
└── package.json
firebase.json
:我們用了兩個服務,所以在當中會看到functions
及database
的設定,這些設定會在模擬器或firebase deploy
部署被使用。.firebaserc
: Firebase 專案 ID,這個 ID 會在firebase deploy
時被使用。database.rules.json
:資料庫的規則,預設為禁止讀取及寫入資料。functions
:放置 Firebase Functions 的目錄,裡面的index.js
,這是主要程式碼,在搭建 functions 時也可以安裝我們想要使用的 JS 套件。
資料庫規則設定
於 database.rules.json
檔案中,我們加入一條 number.read
的規則,讓 number
欄位的資料可以被公開讀取,但無法公開被寫入:
json
{
"rules": {
".read": false,
".write": false,
"number": {
".read": true
}
}
}
functions 開發
進入 functions
目錄後安裝 express
及 cors
兩個套件:
sh
cd functions
pnpm install express cors
然後在 package.json
當中加入 "type": "module"
以支援 ES Module 寫法。
編寫 index.js
:
js
import functions from "firebase-functions";
import admin from "firebase-admin";
import cors from "cors";
import express from "express";
const app = express();
admin.initializeApp();
app.use(cors({ origin: true }));
app.post('/number', async (req, res) => {
const ref = admin.database().ref("number");
const { snapshot } = await ref.transaction(value => (value || 0) + 1);
res.json({ data: snapshot });
});
export const counter = functions.https.onRequest(app);
- 我們已經在
database.rules.json
設定number
欄位為能夠公開讀取,不能公開被寫入,我們要讓寫入的情況在此 API 發生。 - 使用 admin 的身份來讀取
number
的值,並將值 +1 後寫回資料庫。 - 藉由
export const counter
會產生出//.../counter/number
的 API 位置,其中counter
可以當作是我們的 namespace。
本地測試
回到專案根目錄,啟動模擬器:
sh
firebase emulators:start
留意模擬器的提示訊息會看到 database 的 UI 網址 http://127.0.0.1:4000/database ,可以在該頁面觀察資料庫的狀態,如有資料變更都會即時更新:
模擬器的提示訊息也可以看到 API 的位址,於另一個終端機畫面上試戳 API:
sh
# functions 在本地的運行的位置應為 http://127.0.0.1:5001/your-project-id/your-server-region/counter
# 在其結尾加入 /number 後作為 API 的位置
# 戳完以後,正常可以看到資料庫的數字 +1 了
curl -X POST http://127.0.0.1:5001/your-project-id/your-server-region/counter/number
# {"data":4}
部署
當本地開發完成,測試也都如預期後,firebase 方便的一條指令就能將 functions 及 database.rules.json 部署到雲端:
firebase deploy
前端
為了在讓網站前端能使用 Firebase,需要註冊一個前端的應用程式,並取得相關的設定值,以下畫面在 Firebase 的專案畫面中可以找到,點擊「新增應用程式」後依照指示完成註冊:
使用 Vue 來渲染資料
以 Vue 為例,安裝 firebase
後,使用 Firebase Realtime database 的範例:
js
import { initializeApp } from "firebase/app";
import { ref } from "vue";
import { getDatabase, ref as databaseRef, onValue } from "firebase/database";
import { getFunctions, httpsCallable } from "firebase/functions";
export const firebaseApp = initializeApp({
// 使用你的應用程式設定...
});
export const db = getDatabase(firebaseApp);
export const functions = getFunctions(firebaseApp);
export const getDatabaseRef = path => {
const data = ref(null);
const reference = databaseRef(db, path);
onValue(reference, snapshot => {
data.value = snapshot.val();
});
return data;
}
export const incrementCount = httpsCallable(functions, "counter/number");
- 這裡寫了一個工具函數
getDatabaseRef
,可以傳入一個 Database 的資料路徑, 回傳一個Vue
的ref
物件(注意不是 Firebase 的 ref)。 - 使用 onValue 監聽資料變動,callback function 會在第一次取得資料以及之後有資料變更時被呼叫,藉此改變 Vue
ref
的值。 onValue
是新版 JS SDK 的用法,這與幾年前學過舊版DatabaseRef.on("value", ...)
用法不一樣了。- +1 的功能我們可以用
axios
+ API Endpoint 來實作,但 firebase js SDK 已經提供了方便的功能讓我們直接調用 functions,這裡使用了httpsCallable
建立與 API 的連結,所產生的結果可以作為函數直接呼叫使用。
接著在 Vue 中可以這樣使用:
vue
<template>
{{ number }}
<button @click="incrementCount">+1</button>
</template>
<script setup>
import { getDatabaseRef, incrementCount } from "./firebase";
const number = getDatabaseRef("number");
</script>
結語
到目前為止完成了一個簡單的計數功能,可以在前端透過 API 調用來 +1,並且畫面上的數字回即時響應數字的更新。 Firebase 可以讓我們快速開發出一些簡單的功能,而不需要自己架設伺服器,很適合用在一些簡單情境作為輔助,而且計費方式為 Pay as you go,免費額度(每月 200萬次 function calls、每天 360MB DB 下載流量、1GB 的 DB 空間)也足以滿足大部分的需求。
Firebase 的主要功能 functions 其實跟 AWS lambda 就是相似的東西,我個人的體驗是 functions 用起來更方便許多,然後 functions 與 database 是同一個專案下的不同資源,在 functions 當中可以直接使用 database 光是這點就是很大的賣點了,再來部署方面也比 AWS lambda 簡單快速多了。
關於 Firebase functions 以及 Realtime database 有些沒介紹到也值得提出的部分:
- Firebase functions 是產品,背後是用 Google Cloud Functions 包裝,可以因應需求設置自動擴展、timeout、記憶體限制、環境變數、Secret。
- Firebase functions 也支援排程 Schedule functions,直接在程式寫好時間設定,部署後就可以讓他定時自己執行。
- Cloud Functions 目前有兩個版本一、二代的差異。
- Firebase JS SDK 有版本 9 及版本 8 的使用差異,早期學到的用法都是版本 8。
- 同專案下可以有多個 Realtime database 資料庫,可以有各自的規則(database.rules.json),不論在前端或 functions 都可以調用不同的資料庫。