Firebase 教學 - Firestore 安裝、寫入和讀取

Cloud Firestore 是 Google 所提供的新一代即時資料庫,延續 RealTime Database 的高效率以及低延遲性,可以即時監聽資料庫的變化,並同步有使用到資料庫的各個應用,此外,在安全性的定義上也更加彈性和直覺,對於「純前端」工程師來說,是相當好用的資料庫。

延伸閱讀:

建立資料庫

首先我們先用 Google 帳號登入 ( https://firebase.google.com/ ),接著進入 firebase 的控制台,點選「新增專案」,伺服器所在的位置選擇台灣,Cloud Firestore 選擇 asia-east2 ( 單純只是選擇距離台灣比較近的 ),相關條款打勾後即可新增專案。( 如果名稱和別人重複,自訂名稱後方多出一串亂碼 )

專案建立後,會自動進入專案的管理畫面,點選左側選單的「Database」,點選右側 Cloud Firestore 建立資料庫。

一開始建立先使用「以測試模式啟動」,方便一開始寫入或讀取資料,後續篇幅會再介紹安全性規則。

完成後就會看見類似下圖的畫面,表示資料庫建立完成,可以開始使用囉!

初始化

首先載入對應的 Javascript 函式庫 ( 我使用 Google 所提供的連結 )

<script src="https://www.gstatic.com/firebasejs/4.12.1/firebase.js"></script>

<script src="https://www.gstatic.com/firebasejs/4.12.1/firebase-firestore.js"></script>

在 Javascript 寫入下面這些程式碼,Project ID 填入自己的 Project ID,並宣告 db 變數為 Firestore 資料庫,執行後就可完成 Firestore 初始化。

firebase.initializeApp({
  projectId: '### CLOUD FIRESTORE PROJECT ID ###'
});
var db = firebase.firestore();

雖然有別於 Realtime Database 是使用 json 節點的形式,Firestore 採用集合 ( collection )文件 ( doc ) 的概念,集合內會包含文件,文件內也可以包含集合,資料只能存在文件內,整體來說,本質上仍然是 json 格式,但又更靈活有彈性。( 可以把集合想像成 A4 資料夾,文件就是 A4 紙張,資料是寫在 A4 紙上而不是寫在資料夾上 )

寫入資料

Firestore 寫入資料的方式有設定 ( set )、更新 ( update )、添加 ( add ) 和刪除 ( delete ),四種方式都支援 Promise,可以串接 then 監聽是否完成。

1. 設定 set

透過set的方法,可以設定某個資料夾的內容,下面的程式範例執行後,會在 fruit 水果的集合裡,添加 apple 蘋果的文件,並在文件內寫入資料。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.set({
  total: 500,
  good: 480,
  sale: 330
}).then(() => {
  console.log('set data successful');
});

有別於 Realtime Database 的set,firestroe 更提供了merge的方法,幫助我們在寫入資料時不會完全覆蓋,而是進行合併整理,merge的用法只需在 set 的參數後方,新增{merge:true}即可,下方的程式碼,會將 bad 和 eat 的屬性,merge 到原本的文件內。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.set({
  bad:15,
  eat:5
},{merge: true}).then(() => {
  console.log('set data successful');
});

如果沒有指定文件 doc 的名稱,執行後會自動產生一個亂數代碼作為文件名稱。

var db = firebase.firestore();
var ref = db.collection('fruit').doc(); // doc() 沒有指定名稱

ref.set({
  total:200,
  good:200
}).then(() => {
  console.log('set data successful');
});

2. 更新 update

update方法可以指定文件內某個屬性進行更新,避免覆寫整個文件內容 ( 跟上述 merge 有異曲同工之妙 ),下方程式碼執行後,會針對 total 和 good 的屬性做更新。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.update({
  total:450,
  good:400
}).then(() => {
  console.log('update data successful');
});

3. 添加 add

add主要針對「集合」而非「文件」,使用後可以自動添加文件,文件名稱為系統自動產生的亂碼,下方的程式碼在每次執行時,都會自動在 fruit 的集合內添加文件。( 和上述 set 資料夾不指定名稱的用法有異曲同工之妙 )

var db = firebase.firestore();
var ref = db.collection('fruit'); // add() 是針對集合使用

ref.add({
  total:450,
  good:400
}).then(() => {
  console.log('add data successful');
});

4. 刪除 delete

delete方法可以用於刪除集合或是文件,但如果集合裡有文件,則無法刪除集合,下面的程式碼執行後,會刪除 fruit 集合裡的 apple 文件。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.delete().then(() => {
  console.log('delete data successful');
});

如果不是想要刪除整個文件,只是想刪除文件內的某個屬性,則需要透過update的方式來實現,下方程式碼執行後會刪除 apple 文件內的 eat 屬性。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.update({
  eat:firebase.firestore.FieldValue.delete()
}).then(() => {
  console.log('set data successful');
});

讀取資料

Firestore 讀取資料的方式有一次性讀取 ( get ) 和即時監聽變化 ( onSnapshot ) 兩種方法,兩種方法可以搭配篩選 ( where ) 和排序 ( orderBy ) 兩種進一步的篩選,進行更精準的資料取得和比對的前置動作。

在讀取資料前,我先在 firestore 裡建立了一個名為 fruit 的集合,裡面放入 apple、mango、banana 和 orange 四個文件,每個文件都有 total 和 good 的屬性,有些則有 recommend 或 eat 的屬性,待會就會用這份資料來操作。

1. 一次性讀取 get

get的方法可以取得某個集合裡,所有文件的資料 ( 子集合只能在後端讀取,無法在前端或 App 段實現 ),下面的程式碼,執行後可以取得 fruit 裡頭四種水果的名稱和屬性,如果get對象為集合 collection,取得資料後可使用forEach個別取出文件內容,詳細 API 可以參考:firebase. firestore. QuerySnapshot

var db = firebase.firestore();
var ref = db.collection('fruit');

ref.get().then(querySnapshot => {
  querySnapshot.forEach(doc => {
    console.log(doc.id, doc.data());
  });
});

如果直接指定文件的名稱,就可直接取得文件的內容。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.get().then(doc => {
  console.log(doc.data());
});

2. 即時監聽變化 onSnapshot

onSnapshot方法可以即時監聽資料庫的變化,有別於get方法,onSnapshot處在隨時監聽的狀態,只要有變化就會立刻傳送指定的資料,下面的程式執行後,可以取得 fruit 裡頭四種水果的名稱和屬性,這時如果再新增一種水果 grape,就會看到即時出現變化。( 對象為集合 collection,取得資料後可使用forEach個別取出文件內容 )

var db = firebase.firestore();
var ref = db.collection('fruit');

ref.onSnapshot(querySnapshot => {
  querySnapshot.forEach(doc => {
    console.log(doc.id, doc.data());
  });
});

如果直接指定文件的名稱,就可直接取得文件的內容。

var db = firebase.firestore();
var ref = db.collection('fruit').doc('apple');

ref.onSnapshot(doc => {
  console.log(doc.data());
});

3. 篩選 where

讀取資料的下一步就是要處理資料,處理時不免會用到許多邏輯判斷,然而 firestore 提供的where方法,能幫助我們進行第一步的資料篩選,如此一來就能減少並較為精準的讀入的資料,後續處理起來也就更簡單。

where包含三個參數,第一個是屬性,第二個是邏輯運算子 ( 大於、小於...等 ),第三個是參數,會寫在getonSnapshot的前方,下面的程式執行後,會篩選出 fruit 集合裡具有 recommend 為 true 的 doc 文件。

var db = firebase.firestore();
var ref = db.collection('fruit');

ref.where('recommend','==',true).get().then(querySnapshot => {
  querySnapshot.forEach(doc => {
    console.log(doc.id, doc.data());
  });
});

where可以一個串一個的進行複合式的邏輯篩選,如果是==的形式,可以篩選不同的屬性,如果是>>=<=<之類的邏輯範圍,則只能針對單一屬性,下方的程式會篩選出屬性 total 介於 200 到 400 之間的 doc 文件。

使用where做判斷要注意型別,如果資料庫中的數字為 string,則無法使用 number 的邏輯判斷。

var db = firebase.firestore();
  var ref = db.collection('fruit');
  ref.where('total', '>=', 200).where('total', '<=', 400).get().then(querySnapshot => {
    querySnapshot.forEach(doc => {
      console.log(doc.id, doc.data());
    });
  });

4. 排序 orderBy

orderBy通常會和limit搭配,主要作為排序後篩選特定數量的資料,orderBy有兩個參數,第一個是要進行排序的屬性名稱,第二個是遞增 ( asc ) 或遞減 ( desc ),下方程式執行後會篩選出 total 屬性最高的前三名。

var db = firebase.firestore();
var ref = db.collection('fruit');
ref.orderBy('total','desc').limit(3).get().then(querySnapshot => {
  querySnapshot.forEach(doc => {
    console.log(doc.id, doc.data());
  });
});

小結

其實 Cloud Firestore 使用上就跟 Realtime Database 一樣簡單,而且功能還更多,後續篇幅會再繼續介紹 Cloud Firestore 的安全性規則。

有興趣瞧瞧其他新文章嗎?