怎麼用 ES6 初始化 Firebase-Admin?以 Next.js + Vercel 為例。
最近跟 Firebase Admin SDK 惡戰苦鬥的故事。
🕒 published on ー 2022. 1. 15.
Background by OKUMONO
Free Talk
要先認清的幾件事:
- Firebase-Admin SDK 是用在 Server 端,Firebase SDK 是用在 Client 端,這就是它們的差別,詳細可看 Bringing Firebase To Your Server 這篇官方部落格。
- Next.js 裡 屬於 Server Side 的地方就要用 Admin,例如想在
getStaticProps()
裡跟 Firestore 溝通,就要用 Admin;反之,若在屬於 Client Side 的地方用到 Admin,就會獲得精美報錯。 - 承上,所以官方文件範例記得要看 Node.js 那個 Tab 的才對。
- 再承上,所以初始化,官方文件的初始化教學是看 Add Firebase to a server 這篇。
- 官方文件最好還是看英文的,中文版是它自家 Google 雲翻譯機翻的,雖然說不上翻錯但我看了血壓是有點上升 w 它把 Firebase 本人翻成 火力堡、Node.js 變 節點.js、token 變 令牌。
邊犯蠢、邊踩坑才知道這些事情的就是我,認為很值得筆記起來 LOL, 對了,接下來並不會逐字解說官方文件,主是記下我踩到的坑 & 我的做法而已。
為什麼我要用 ES6 語法來初始化 Firebase-Admin?
2021/10/14 的 Firebase Admin Node.js SDK Release Notes 寫說 Admin 自 v10.0.0 開始也改用 ES6 Module 了, 以往的舊版寫法在未來某個時間點將會被淘汰掉,剛好最近我在家玩 Next.js 時想導入 Admin 來用,距離我上次跟 Firebase 打交道已時隔兩年,覺得那就索性忘掉過去(其實早就忘了#),向新版寫法看齊吧。
以上,沒什麼大不了的原因 w 就一個遲早要升版的感覺。
坑之 1:有關 Google Application Credentials
Google Application Credentials 其實就是一個 JSON 檔,內容寫著 API Key 之類初始化需要用到的機密資料。 但按照官方文件的初始化教學, 也就是我在 Free Talk 第四點貼的 Add Firebase to a server 這篇, 它叫大家把這個 JSON 檔存在自己電腦某處,然後寫上該檔案存放的路徑來初始化。
由於我之後打算把 Next.js 專案部署到 Vercel 上,必須告訴 Vercel 這些機密資料才行,那…該怎麼辦? 官方文件沒教到這個,未來應該不會也沒義務教,但總不是叫 Vercel 通靈、觀落硬碟找到我的 JSON 檔吧(X),只好還是去估狗了舊版寫法,從中推敲。
// 這是舊版寫法唷,在我撰文的當下(2022-01-15)還是可以用的啦。
import * as firebaseAdmin from 'firebase-admin';
if (!firebaseAdmin.apps.length) {
const adminCredentials = {
credential: firebaseAdmin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.PRIVATE_KEY),
}),
databaseURL: env.process.DATABASE_URL,
};
firebaseAdmin.initializeApp(adminCredentials)
};
export default firebaseAdmin
我觀察到:
- 應該能把 Google Application Credential 那個 JSON 檔裡寫的秘密,放進環境變數裡,且不需要每個項目都用上。
- 用 Firestore 不需給 Database Url;用 Realtime Database 才要給。 看官方文件有一句 ★ Note 寫說 Database Url 並非初始化必要的項目,加上 Firestore 的主控台頁面到處找不到寫網址的地方,但 Realtime Database 的主控台頁面就找得到這個網址,就能瞭解了。
- 舊版寫法大家是用 apps.length 來檢查有無重複初始化。
坑之 2:啊咧,那用新版寫法要怎麼檢查是否重複初始化?
正所謂坑坑相扣(?),官方文件也沒有寫到這點,但我推測 apps.length
的這個 apps
,從前就是個在 Module 裡的東西,現在應該也還是,既然如此就應該有辦法叫出來用吧!
果不其然,在 firebase-admin/app module 這篇介紹 App Moudule 的官方文件裡,
可以發現 App Module 裡面有 getApps()
跟 getApp()
這兩個 Function,前者回傳的就是 apps
、後者回傳 APP 本人,
知道有這兩個 Function 後,總算可以來填坑了。
兩個坑一起填了!
經過上述的一番研究,初始化 Firebase-Admin 的 ES6 新版寫法就此誕生:
import { initializeApp, getApps, getApp, cert } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
const serviceAccount = {
type: process.env.FIREBASE_TYPE,
project_id: process.env.FIREBASE_PROJECT_ID,
private_key_id: process.env.FIREBASE_PRIVATE_KEY_ID,
private_key: process.env.FIREBASE_PRIVATE_KEY,
client_email: process.env.FIREBASE_CLIENT_EMAIL,
client_id: process.env.FIREBASE_CLIENT_ID,
auth_uri: process.env.FIREBASE_AUTH_URI,
token_uri: process.env.FIREBASE_TOKEN_URI,
auth_provider_x509_cert_url: process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL,
client_x509_cert_url: process.env.FIREBASE_CLIENT_X509_CERT_URL,
};
const firebaseAdmin =
getApps().length === 0
? initializeApp({
credential: cert(serviceAccount),
})
: getApp();
const db = getFirestore(firebaseAdmin);
export default db;
簡短解說,分幾個步驟:
- 我先在 Next.js 專案裡的
.local.env
檔裡先設好環境變數,要用的東西 import 起來備用 - 製作
serviceAccount
物件,要給幫忙初始化的initializeApp()
、cert()
吃的,Google Application Credential 那個 JSON 檔的內容,我還是每個項目都餵了, 因為我經歷了一陣報錯風暴,Try 到最後成功的版本就是寫這樣,然後就不想再改了 XD 理論上只用 Firestore 的話應該跟舊版一樣只餵projectId
、clientEmail
、privateKey
就好啦。 - 初始化跟寫一些判斷:如果
apps
裡面已經有東西,那就用清單裡的那個 APP,否則初始化一個。 - 召喚我的 Firestore 出來放進一個叫
db
的變數裡,然後 export 它,讓我能在有需要的地方使用。
大致是這樣,我可是…花了兩天才搞定呀(我就爛.jpg)。
坑之 3:Invalid PEM formatted message
一坑未平一坑又起啊,這個錯誤是我在填上面兩個坑時冒出來的,這個錯誤訊息主要是告訴人說 Private Key 的格式不對,所以我可以肯定上面那段初始化 Code 是 OK 的,有問題的是環境變數或是 Private Key 本人!
但…我 Private Key 是從 Google Application Cre dential 那個 JSON 檔複製貼上的,環境變數寫法我也確定沒錯呀,怎麼會這樣哩?
話又說回來,那 Private Key 無敵爆長一串的,還有一堆換行符號 \n
在裡面(這是要人真的換行),開頭結尾還有註解 -----BEGIN PRIVATR KEY-----
、 -----END PRIVATE KEY-----
(這也是 Private Key 的一部分不能刪),484 在擾民。
估狗發現還真不少人踩到這個坑,幸好問得人多,解答的人也就多,歸類查到的 3 種解法:
- 給環境變數加雙/單引號,例如:
FIREBASE_PRIVATE_KEY="………"
,因為 dotenv 或 Next.js 本身也內建有會處理環境變數的東西,看到引號就會知道「喔好我自動幫你換行」,不會傻傻把\n
當成字串。 參考:規則不公開狀態下控制 firestore - 環境變數不加任何引號照常寫,使用時尾巴加寫個
replace()
把換行符號轉成真的換行,例如:const private_key = process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')
。 參考:Node.js -Firebase Service Account Private Key won’t parse - 把 Private Key 轉成 base64,甚至把 Google Application Credential 那整個 JSON 檔都轉成 base64,吃我的 base64 啦! 參考:Accessing Google Firestore on Vercel
我先試了解法 1、2,一下通通不行,一下 Local 行、部署到 Vercel 時不行!只好試了解法 3,結果 Vercel 不接受環境變數裡塞了 base64 這麼長的字串 w 到底該怎辦!不然環境變數刪了重寫,再打開 Google Application Credential 那個 JSON 檔複製貼上一次看看好了?
然後,就都好了…。就都沒有問題了!Why!我不能接受,非得找出原因。
坑之 4:謎底揭曉
經過一番調查,找到了這篇文章:Firebase (Server-Side),畫面拉到最底下有一句:
Note the extra new line at the end.
…原來,Private Key 最後 -----END PRIVATE KEY-----
後面還有一個 \n
,其實是 -----END PRIVATE KEY-----\n
,
不曉得何時誤把最後那個 \n
給刪了!加上很多文章範例最後也都是虛號結尾,就沒多想,難怪什麼解法都沒用。
想必寫那篇文章的人也犯過跟我一樣的蠢吧(別牽拖#)
最後回去用解法 1 ,不管是在 Loacal 還是 Vercel 總算都給我 Success,1 天又平安度過。
然而我總計花了 3 天才完全搞定!
Ending
那麼以上故事結束,希望寫下來或多或少能幫到人吧。 補充個,在 Vercel 潮潮的網站上用 GUI 設定環境變數的時候, 直接複製貼上不用多做任何動作就行,可參考它官方文件 Environment Variables,那麼就這樣。