Membaca dan Menulis Data di Web

(Opsional) Membuat prototipe dan melakukan pengujian dengan Firebase Local Emulator Suite

Sebelum membahas cara aplikasi Anda membaca dari dan menulis ke Realtime Database, kami akan memperkenalkan serangkaian alat yang dapat digunakan untuk membuat prototipe dan menguji fungsionalitas Realtime Database: Firebase Local Emulator Suite. Jika Anda sedang mencoba berbagai model data, mengoptimalkan aturan keamanan, atau berupaya menemukan cara yang paling hemat untuk berinteraksi dengan backend, kemampuan untuk bekerja secara lokal tanpa men-deploy layanan langsung dapat sangat bermanfaat.

Emulator Realtime Database adalah bagian dari Local Emulator Suite yang memungkinkan aplikasi berinteraksi dengan konfigurasi dan konten database yang diemulasi, serta secara opsional dengan resource project yang diemulasi (fungsi, database lain, dan aturan keamanan).

Anda hanya perlu beberapa langkah untuk menggunakan emulator Realtime Database:

  1. Menambahkan satu baris kode ke konfigurasi pengujian aplikasi untuk terhubung ke emulator.
  2. Menjalankan firebase emulators:start dari root direktori project lokal Anda.
  3. Melakukan panggilan dari kode prototipe aplikasi Anda menggunakan SDK platform Realtime Database seperti biasa, atau menggunakan Realtime Database REST API.

Panduan mendetail yang mencakup Realtime Database dan Cloud Functions telah tersedia. Sebaiknya baca juga Pengantar Local Emulator Suite.

Mendapatkan referensi database

Untuk membaca atau menulis data dari database, Anda memerlukan instance firebase.database.Reference:

API modular web

import { getDatabase } from "firebase/database";

const database = getDatabase();

API dengan namespace web

var database = firebase.database();

Menulis data

Dokumen ini mencakup dasar-dasar pengambilan data, serta cara mengurutkan dan memfilter data Firebase.

Data Firebase diambil dengan menambahkan pemroses asinkron ke firebase.database.Reference. Pemroses dipicu sekali untuk status awal data, dan dipicu kembali setiap kali data berubah.

Operasi tulis dasar

Untuk operasi tulis dasar, Anda dapat menggunakan set() untuk menyimpan data ke referensi yang ditentukan, sehingga menggantikan data yang ada di jalur tersebut. Misalnya, aplikasi blogging sosial dapat menambahkan pengguna dengan set() seperti berikut:

API modular web

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

API dengan namespace web

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Jika set() digunakan, data di lokasi yang ditentukan akan ditimpa, termasuk semua node turunan.

Membaca data

Memproses peristiwa nilai

Untuk membaca data di suatu jalur dan memproses perubahan, gunakan onValue() untuk mengamati peristiwa. Anda dapat menggunakan peristiwa ini untuk membaca snapshot statis konten di jalur tertentu, sesuai keberadaan konten tersebut saat peristiwa terjadi. Metode ini terpicu satu kali ketika pemroses terpasang, dan terpicu lagi setiap kali terjadi perubahan pada data, termasuk pada setiap turunannya. Callback peristiwa mendapatkan snapshot yang berisi semua data di lokasi tersebut, termasuk data turunan. Jika tidak ada data, snapshot akan menampilkan false ketika exists() dipanggil, serta menampilkan null ketika val() dipanggil pada snapshot tersebut.

Contoh berikut menunjukkan aplikasi blogging sosial yang mengambil jumlah bintang suatu postingan dari database:

API modular web

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

API dengan namespace web

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Pemroses akan menerima snapshot yang berisi data di lokasi yang ditentukan dalam database saat peristiwa terjadi. Anda dapat mengambil data dalam snapshot dengan metode val().

Membaca data sekali

Membaca data sekali dengan get()

SDK ini didesain untuk mengelola interaksi dengan server database, baik saat aplikasi Anda online maupun offline.

Biasanya, Anda harus menggunakan teknik peristiwa nilai yang dijelaskan di atas untuk membaca data agar mendapatkan notifikasi terkait pembaruan data dari backend. Teknik pemroses mengurangi penggunaan dan penagihan Anda, serta dioptimalkan untuk memberikan pengalaman terbaik kepada pengguna saat mereka online dan offline.

Jika hanya memerlukan data satu kali, Anda dapat menggunakan get() untuk mendapatkan snapshot data dari database. Jika karena alasan apa pun get() tidak dapat menampilkan nilai server, klien akan menyelidiki cache penyimpanan lokal dan menampilkan error jika nilainya masih belum ditemukan.

Penggunaan get() yang tidak perlu dapat meningkatkan penggunaan bandwidth dan menyebabkan penurunan performa. Ini dapat dicegah menggunakan pemroses realtime seperti yang ditunjukkan di atas.

API modular web

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

API dengan namespace web

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Membaca data sekali dengan observer

Dalam beberapa kasus, Anda mungkin menginginkan nilai dari cache lokal langsung ditampilkan, daripada memeriksa nilai yang diperbarui di server. Dalam kasus tersebut, Anda dapat menggunakan once() untuk langsung mendapatkan data dari cache disk lokal.

Cara ini berguna untuk data yang hanya perlu dimuat sekali, dan tidak diharapkan untuk sering berubah atau memerlukan pemrosesan aktif. Misalnya, aplikasi blogging pada contoh sebelumnya menggunakan metode ini untuk memuat profil pengguna ketika mulai membuat postingan baru:

API modular web

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

API dengan namespace web

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

Memperbarui atau menghapus data

Memperbarui kolom tertentu

Untuk menulis secara simultan ke turunan tertentu sebuah node tanpa menimpa node turunan yang lain, gunakan metode update().

Saat memanggil update(), Anda dapat memperbarui nilai turunan di level yang lebih rendah dengan menentukan jalur untuk kunci. Jika data disimpan dalam beberapa lokasi agar dapat melakukan penskalaan yang lebih baik, Anda dapat memperbarui semua instance data tersebut menggunakan fan-out data.

Misalnya, aplikasi blogging sosial dapat membuat postingan dan mengupdatenya secara bersamaan ke feed aktivitas terbaru dan feed aktivitas pengguna pengirim postingan menggunakan kode seperti ini:

API modular web

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

API dengan namespace web

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

Contoh ini menggunakan push() untuk membuat postingan dalam node yang berisi postingan bagi semua pengguna di /posts/$postid dan sekaligus mengambil kunci. Selanjutnya, kunci tersebut dapat digunakan untuk membuat entri kedua di postingan pengguna pada /user-posts/$userid/$postid.

Dengan menggunakan jalur tersebut, Anda dapat menjalankan pembaruan simultan ke beberapa lokasi di hierarki JSON dengan satu panggilan ke update(), seperti yang digunakan pada contoh ini untuk membuat postingan baru di kedua lokasi. Pembaruan simultan yang dilakukan dengan cara ini bersifat atomik: semuanya akan berhasil atau semuanya akan gagal.

Menambahkan Callback Penyelesaian

Jika ingin mengetahui kapan data di-commit, Anda bisa menambahkan callback penyelesaian. set() dan update() menggunakan callback penyelesaian opsional yang akan dipanggil ketika operasi tulis telah di-commit ke database. Jika panggilan tidak berhasil, objek error akan diteruskan ke callback untuk menunjukkan penyebab kegagalan.

API modular web

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

API dengan namespace web

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

Menghapus data

Cara termudah untuk menghapus data adalah dengan memanggil remove() pada referensi ke lokasi data tersebut.

Penghapusan juga dapat dilakukan dengan menentukan null sebagai nilai untuk operasi tulis lainnya, seperti set() atau update(). Teknik ini dapat digunakan dengan update() untuk menghapus beberapa turunan dengan satu panggilan API.

Menerima Promise

Untuk mengetahui kapan data di-commit ke server Firebase Realtime Database, Anda dapat menggunakan Promise. set() dan update() dapat menampilkan Promise yang dapat Anda gunakan untuk mengetahui kapan penulisan di-commit ke database.

Melepas pemroses

Callback akan dihapus dengan memanggil metode off() pada referensi database Firebase.

Anda dapat menghapus sebuah pemroses dengan meneruskannya sebagai parameter ke off(). Memanggil off() pada lokasi tanpa argumen akan menghapus semua pemroses yang ada di lokasi tersebut.

Memanggil off() pada pemroses induk tidak akan otomatis menghapus pemroses yang terdaftar pada node turunannya. off() juga harus dipanggil pada pemroses turunan mana pun untuk menghapus callback.

Menyimpan data sebagai transaksi

Ketika menangani data yang bisa rusak karena perubahan serentak, seperti penghitung pertambahan inkremental, Anda dapat menggunakan operasi transaksi. Anda dapat memberi operasi ini fungsi pembaruan dan callback penyelesaian opsional. Fungsi pembaruan mengambil status data saat ini sebagai argumen, dan akan menampilkan status baru yang ingin Anda tulis. Jika klien lain melakukan operasi tulis ke lokasi ini sebelum nilai baru Anda berhasil ditulis, fungsi pembaruan Anda akan dipanggil lagi dengan nilai saat ini yang baru, dan operasi tulis akan dicoba ulang.

Misalnya, pada contoh aplikasi blogging sosial, Anda dapat mengizinkan pengguna memberi atau menghapus bintang pada postingan, serta memantau jumlah bintang yang telah diterima suatu postingan dengan cara berikut ini:

API modular web

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

API dengan namespace web

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Penggunaan transaksi dapat mencegah kesalahan penghitungan jumlah bintang jika beberapa pengguna memberi bintang pada postingan yang sama secara bersamaan, atau jika klien memiliki data yang sudah usang. Jika transaksi ditolak, server akan menampilkan nilai saat ini ke klien, yang akan mengulangi transaksi tersebut dengan nilai yang telah diperbarui. Proses ini akan berulang sampai transaksi diterima atau Anda membatalkan transaksi.

Pertambahan inkremental atomik sisi server

Dalam kasus penggunaan di atas, kita menulis dua nilai ke database: ID pengguna yang memberi/menghapus bintang pada postingan, dan pertambahan inkremental jumlah bintang. Jika sudah mengetahui bahwa pengguna memberi bintang pada postingan, kita dapat menggunakan operasi pertambahan inkremental atomik, bukan transaksi.

API modular web

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

API dengan namespace web

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().ref().update(updates);
}

Kode ini tidak menggunakan operasi transaksi, sehingga tidak otomatis dijalankan ulang jika ada pembaruan yang bertentangan. Namun, karena operasi pertambahan inkremental terjadi langsung di server database, tidak ada kemungkinan konflik.

Jika ingin mendeteksi dan menolak konflik khusus aplikasi, misalnya pengguna memberi bintang pada postingan yang sebelumnya telah dibintanginya, Anda harus menulis aturan keamanan khusus untuk kasus penggunaan tersebut.

Menangani data secara offline

Jika koneksi jaringan klien terputus, aplikasi Anda akan tetap berfungsi dengan baik.

Setiap klien yang terhubung ke database Firebase menyimpan versi internalnya sendiri untuk setiap data aktif. Ketika ditulis, data akan dituliskan ke versi lokal ini terlebih dahulu. Selanjutnya, klien Firebase menyinkronkan data tersebut dengan server remote database, dan dengan klien lain berdasarkan "upaya terbaik".

Akibatnya, semua operasi tulis ke database akan langsung memicu peristiwa lokal, sebelum ada data yang dituliskan ke server. Ini berarti aplikasi Anda akan tetap responsif, apa pun kondisi konektivitas atau latensi jaringannya.

Setelah terhubung kembali ke jaringan, aplikasi Anda akan menerima kumpulan peristiwa yang sesuai agar klien melakukan sinkronisasi dengan kondisi server saat ini, tanpa harus menulis kode khusus.

Kita akan membahas lebih lanjut perilaku offline dalam artikel Mempelajari lebih lanjut kemampuan online dan offline.

Langkah berikutnya