【Firebase】好好用的Functions功能,自己做WebAPI
相信大家多少都有聽過Firebase是個JSON格式的資料庫吧?但是其實它還有一些其它的功能,比如說今天要介紹的這個Functoins的功能,就是拿來寫WebAPI用的,可以直接去讀取Firebase資料庫,相當的方便,它有支援iOS / Android / JavaScript / C++ / Unity的Framework - onCall(),但個人是要把它做成HTTP的形式 - onRequest(),為什麼呢?一方面是因為並不是每一個語言都有Firebase的支援,另一方面是比較容易看到輸出的結果。現在我們就來試試看吧。
安裝過程
Functions在哪裡?
- 首先,我們先來看看Functions到底在哪裡呢?登入firebase console後,就可以看到了喲。
安裝Functions
- 首先要要安裝nodejs,然後安裝Firebase CLI
node -v
npm install -g firebase-tools
登入CLI
- 當然要有個gmail帳號用來登入Firebase Functions CLI,接下來就是按影片施工,保證成功。
firebase login
建立新專案
- 利用CLI建立一個新專案,但記得一定要login,不然是沒有辦法產生的。
firebase init
功能實作
Helloworld
- 主要的code都是放在index.js上面,它是functions的進入點。
// index.js
const functions = require('firebase-functions');
// helloWorld() => 印出中文字
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("哈囉你好嗎,衷心感謝,珍重再見,期待再相逢")
})
firebase deploy
部署程式
- 將程式上傳到firebase上的時候,不一定要一次上傳全部,也可以一次上傳某幾個,比較要注意的是,如果使用「module.exports」建立多個function,而不是用「exports」建立單一個function的話,只有「module.exports」才有反應,就看看大家習慣哪一種了。
const functions = require('firebase-functions');
// helloWorld() => 印出中文字
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("哈囉你好嗎,衷心感謝,珍重再見,期待再相逢")
})
exports.helloWorld2 = functions.https.onRequest((request, response) => {
response.send("哈囉你好嗎,衷心感謝,珍重再見,期待再相逢")
})
// module.exports
module.exports = {
helloWorld3: functions.https.onRequest((request, response) => {
response.send("哈囉你好嗎,衷心感謝,珍重再見,期待再相逢")
}),
helloWorld4: functions.https.onRequest((request, response) => {
response.send("哈囉你好嗎,衷心感謝,珍重再見,期待再相逢")
})
}
firebase deploy # 全部上傳
firebase deploy --only functions:<functionA>,<functionB> # 上傳某幾個Function
匯入資料
- 這個資料主要是來由於博客來的書籍資料來當成DEMO
{
"eBook": {
"9789861365404": {
"name": "勇氣圖鑑:受挫、失敗都是成長的養分",
"url": "https://www.books.com.tw/products/0010831766",
"prices": [320, 253, 202],
"order": 1
},
"9789578640856": {
"name": "不插電 小學生基礎程式邏輯訓練繪本全套四冊",
"url": "https://www.books.com.tw/products/0010818767",
"prices": [1440, 1080],
"order": 3
},
"9789869744560": {
"name": "關於工作的9大謊言",
"url": "https://www.books.com.tw/products/0010832091",
"prices": [420, 332],
"order": 2
},
"9789570853551": {
"name": "全圖解!AI知識一本通:用故事讓你三小時輕鬆搞懂人工智慧",
"url": "https://www.books.com.tw/products/0010830584",
"prices": [280, 221],
"order": 4
},
"9789863774938": {
"name": "星座決定我愛你:用占星和塔羅幫你找到真命天子or女",
"url": "https://www.books.com.tw/products/0010825318",
"prices": [320, 253, 177],
"order": 5
}
}
}
讀取資料 (Select)
根據路徑讀取資料
- 讀取Firebase上的資料,主要是要require(“firebase-admin”),利用admin來讀取Firebase上的資料。
const functions = require('firebase-functions');
const admin = require("firebase-admin")
admin.initializeApp()
// MARK: - 輸出的Function名稱
module.exports = {
helloWorld: functions.https.onRequest((request, response) => {
let string = getHelloWorld()
response.send(string)
}),
bookList: functions.https.onRequest((request, response) => {
getBookList('eBook', (snapshot) => {
response.send(snapshot)
})
})
}
// MARK: - 小工具
/// helloWorld() => 印出中文字
function getHelloWorld() {
return ("哈囉你好嗎,衷心感謝,珍重再見,期待再相逢")
}
/// 取得全部的書籍資訊
function getBookList(path, callback) {
let readType = 'value'
admin.database().ref(path).once(readType, (snapshot) => {
callback(snapshot)
})
}
讀取排序後的資料
- 這裡主要是利用「orderByChild()」來排序資料內的順序,此外要注意的是,因為是「排序過的」資料,所以要轉成「Array」才能得到正確的資料。
const functions = require('firebase-functions');
const admin = require("firebase-admin")
admin.initializeApp()
// MARK: - 輸出的Function名稱 (給Firebase Functions看的)
module.exports = {
bookListOrderByChild: functions.https.onRequest((request, response) => {
getBookListOrderByChild('eBook', request, (snapshot) => { response.send(snapshot) })
}),
}
// MARK: - 主工具
/// 根據子Key排序後,取得全部的書籍資訊 => Array (https://<host>/bookListOrderByChild?child=name)
function getBookListOrderByChild(path, request, callback) {
_getBookListOrderByChild(path, request, (snapshot) => {
let items = []
snapshot.forEach(item => {
items.push(item)
})
callback(items)
})
}
// MARK: - 小工具
/// 根據子Key排序後,取得全部的書籍資訊
function _getBookListOrderByChild(path, request, callback) {
let readType = 'value'
let child = request.query.child
admin.database().ref(path).orderByChild(child).once(readType, (snapshot) => {
callback(snapshot)
})
}
讀取範圍內的資料
- 在這裡是將「排序過」的資料做範圍的截取,然後加了一欄叫做「order」用它來排序,它主要是利用「startAt() / endAt()」,可以去有效篩選部分的資料。在這裡大家可以在上一個例子中發現,排完序之後,key(ISBN)值就不見了,因為變成有順序的Array,而不是無順序的Object / Dictionary,所以在這裡我們再把key加進去裡面。
// MARK: - 輸出的Function名稱 (給Firebase Functions看的)
module.exports = {
bookListOrderByKeyOfRange: functions.https.onRequest((request, response) => {
getBookListOrderByKeyOfRange('eBook', request, (array) => { response.send(array) })
}),
}
/// 根據子Key排序後,取得該範圍內的書籍資訊 => Array (https://<host>/bookListOrderByKeyOfRange?child=order&start=2&end=4)
function getBookListOrderByKeyOfRange(path, request, callback) {
_getBookListOrderByKeyOfRange(path, request, (snapshot) => {
let items = snapshotToArray(snapshot)
callback(items)
})
}
/// 根據子Key排序後,取得該範圍內的書籍資訊
function _getBookListOrderByKeyOfRange(path, request, callback) {
let readType = 'value'
let child = request.query.child
let start = parseInt(request.query.start)
let end = parseInt(request.query.end)
admin.database().ref(path).orderByChild(child).startAt(start).endAt(end).once(readType, (snapshot) => {
callback(snapshot)
})
}
/// 將snapshot => Array
function snapshotToArray(snapshot) {
let items = []
let dict = snapshot.val()
for (let key in dict) {
let _item = dict[key]
_item['identify'] = key // _item.identify = key
items.push(_item)
}
return items
}
TypeScript的版本
- 其實TypeScript也沒有想像中的難學,不過在JavaScript亂寫都會對的情形就不太會存在了。
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp()
const eventType: admin.database.EventType = 'value'
// MARK: - 輸出的Function名稱 (給Firebase Functions看的)
module.exports = {
tsHelloWorld: helloWorld(),
tsBookList: bookList(),
tsBookListOrderByChild: bookListOrderByChild(),
tsBookListOrderByKeyOfRange: bookListOrderByKeyOfRange(),
}
// MARK: - 主程式
/// 印出中文字 (https://<host>/tsHelloWorld)
function helloWorld(): functions.HttpsFunction {
return functions.https.onRequest((request, response) => {
response.send(getHelloWorld())
})
}
/// 取得全部的書籍資訊 (https://<host>/tsBookList)
function bookList(): functions.HttpsFunction {
return functions.https.onRequest((request, response) => {
getBookList('eBook', (snapshot) => { response.send(snapshot) })
})
}
/// 取得排序後的書籍資訊 (https://<host>/tsBookListOrderByChild?child=name)
function bookListOrderByChild() {
return functions.https.onRequest((request, response) => {
getBookListOrderByChild('eBook', request, (snapshot) => { response.send(snapshot) })
})
}
/// 取得排序後在範圍內的書籍資訊 (https://<host>/tsBookListOrderByKeyOfRange?child=order&start=2&end=4)
function bookListOrderByKeyOfRange() {
return functions.https.onRequest((request, response) => {
getBookListOrderByKeyOfRange('eBook', request, (snapshot) => { response.send(snapshot) })
})
}
// MARK: - 主工具
/// 取得中文字
function getHelloWorld(): Object {
return ({ helloWorld: "哈囉你好嗎,衷心感謝,珍重再見,期待再相逢" })
}
/// 取得全部的書籍資訊
function getBookList(path: string, callback: (snapshot: admin.database.DataSnapshot) => void) {
admin.database().ref(path).once(eventType).then((snapshot) => {
callback(snapshot)
}).catch((reason) =>
console.log(reason)
)
}
/// 根據子Key排序後,取得全部的書籍資訊
function getBookListOrderByChild(path: string, request: functions.https.Request, callback: (snapshot: any[]) => void) {
_getBookListOrderByChild(path, request, (snapshot) => {
callback(snapshotToArray(snapshot))
})
}
/// 根據子Key排序後,取得該範圍內的書籍資訊
function getBookListOrderByKeyOfRange(path: string, request: functions.https.Request, callback: (snapshot: any[]) => void) {
_getBookListOrderByKeyOfRange(path, request, (snapshot) => {
callback(snapshotToArray(snapshot))
})
}
// MARK: - 小工具
/// 根據子Key排序後,取得全部的書籍資訊
function _getBookListOrderByChild(path: string, request: functions.https.Request, callback: (snapshot: admin.database.DataSnapshot) => void) {
const child = request.query.child as string
admin.database().ref(path).orderByChild(child).once(eventType).then((snapshot) => {
callback(snapshot)
}).catch((error) =>
console.log(error)
)
}
/// 根據子Key排序後,取得該範圍內的書籍資訊
function _getBookListOrderByKeyOfRange(path: string, request: functions.https.Request, callback: (snapshot: admin.database.DataSnapshot) => void) {
const parameter = {
child: request.query.child as string,
start: parseInt(request.query.start),
end: parseInt(request.query.end),
}
admin.database().ref(path).orderByChild(parameter.child).startAt(parameter.start).endAt(parameter.end).once(eventType).then((snapshot) => {
callback(snapshot)
}).catch((error) =>
console.log(error)
)
}
/// 將snapshot => Array
function snapshotToArray(snapshot: admin.database.DataSnapshot): any[] {
const items: any[] = []
snapshot.forEach((item) => {
const dict = item.val()
dict.identify = item.key
items.push(dict)
})
return items
}
console.log()
- 大家有沒有發現到,萬一有錯的時候要怎麼debug呢?最基本的console.log()怎麼印不出來呢?其實可以利用「firebase functios」的記錄來看,或者是使用CLI的方式也是可以的。另外不知道有沒有發現,code裡面都沒有「分號;」結尾,這是為什麼呢?大家可以看看這篇文章就能理解了。
firebase functions:log # 列出全部的log
firebase functions:log --only <function1>,<function2> # 列出部分的log
網頁前端的console.log()
- 到現在我才知道,console.log()有這麼多的功能,趕快記起來。
/* MARK: - 一般 */
console.log('我是中文字') // 列印中文字
console.log({ id: 1, name: 'Willam' }) // 列印Object
console.info('我是info') // 訊息列印
console.error('我是error') // 錯誤列印
// console.clear() // 清除log
// console.trace() // 追踨軌跡
/* MARK: - 群組 */
console.group('Group')
console.log('user: William')
console.group('Group A')
console.log('book: JS Book')
console.log('ISBN: 978777555777')
console.groupEnd()
console.group('Group B')
console.log('book: JS Book')
console.log('ISBN: 978777555777')
console.groupEnd()
console.groupEnd()
/* MARK: - 表格 */
console.table({ id: 1, name: 'Willam' })
/* MARK: - 時間差 */
console.time()
for (let index = 0; index < 100000; index++) {}
console.timeEnd()
範例程式碼下載
後記
- Firebase Functions是一個做API滿友好的工具,又結合了Firebase Database,加上它也能夠有推播的功能、Hosting的網頁功能,等於是前後台全包了,Google真的是太可怕了呀。其實Firebase是用買的呀。另外呢,還發現了一個好用的字型 - fira code,大家也可以試用看看,尤其在寫JavaScript特別的明顯好用呢。此外,真心覺得JavaScript真的好寫難debug,寫錯了要到Run的時候才會發現,看來也許要用TypeScript的版本來寫寫看了,最近滿流行的,真的覺得強型別 - Strong Type的語言還是比較適合我呀。