Firebase Realtime Database Intro

Firebase Realtime Database was Firebase's original database, launched years before Firestore. It stores all data as one large JSON tree and syncs changes to connected clients in milliseconds. While Firestore is now the recommended choice for most apps, Realtime Database still excels in specific situations — especially apps that require extremely low-latency sync.

The JSON Tree Structure

Unlike Firestore's collection-document model, Realtime Database stores everything as a single nested JSON object:

{
  "users": {
    "uid_alice": {
      "name": "Alice",
      "score": 1500
    },
    "uid_bob": {
      "name": "Bob",
      "score": 1200
    }
  },
  "messages": {
    "msg_001": {
      "text": "Hello!",
      "authorId": "uid_alice",
      "timestamp": 1720000000000
    }
  }
}

Every piece of data lives at a path in this tree. The path users/uid_alice/name points to the string "Alice". Reading any node also reads all its children, which is why deep nesting causes performance problems — you fetch more data than you need.

Setting Up Realtime Database

In the Firebase Console, go to Realtime Database and click Create Database. Choose a location and a security mode (test or locked). Firebase gives you a database URL like https://my-project-default-rtdb.firebaseio.com.

Install the SDK and initialize:

import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";

const app = initializeApp(firebaseConfig);
export const rtdb = getDatabase(app);

Writing Data

import { ref, set, push, update } from "firebase/database";
import { rtdb } from "./firebase";

// Write at a specific path
await set(ref(rtdb, "users/uid_alice"), {
  name: "Alice",
  score: 1500
});

// Push a new child with auto-generated key
const newMsgRef = push(ref(rtdb, "messages"));
await set(newMsgRef, {
  text: "Hello!",
  authorId: "uid_alice",
  timestamp: Date.now()
});

// Update specific fields
await update(ref(rtdb, "users/uid_alice"), { score: 1600 });

Reading Data Once

import { ref, get } from "firebase/database";
import { rtdb } from "./firebase";

const snapshot = await get(ref(rtdb, "users/uid_alice"));
if (snapshot.exists()) {
  console.log(snapshot.val()); // { name: "Alice", score: 1500 }
} else {
  console.log("No data found.");
}

Real-Time Listening

import { ref, onValue, off } from "firebase/database";
import { rtdb } from "./firebase";

const userRef = ref(rtdb, "users/uid_alice");

const unsubscribe = onValue(userRef, (snapshot) => {
  if (snapshot.exists()) {
    console.log("Updated data:", snapshot.val());
  }
});

// Stop listening
off(userRef);

Deleting Data

import { ref, remove } from "firebase/database";

await remove(ref(rtdb, "messages/msg_001"));

Keep Data Flat

The most important design rule for Realtime Database: keep your data structure as flat as possible. Reading a node downloads all its children. A deeply nested structure means fetching far more data than a specific query needs.

BAD (deeply nested):
users/uid_alice/posts/post_001/comments/comment_001/text

GOOD (flat structure):
users/uid_alice/name
posts/post_001/title
comments/comment_001/text   <-- store postId as a field inside comment

Key Takeaway

Realtime Database stores data as a JSON tree and syncs changes instantly across all connected clients. It is simpler than Firestore but requires careful flat data modeling to avoid downloading unnecessary data. Use it for extremely latency-sensitive features like multiplayer game state or live presence indicators. For most other use cases, Firestore's structured queries and better scaling make it the better choice.

Leave a Comment

Your email address will not be published. Required fields are marked *