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.
