Realtime Database
Realtime Database is Firebase’s original JSON tree database. Push and listen to changes in real time; offline-first; horizontally scaled. Older sibling of Firestore — simpler model, lower per-read cost for some workloads.
Setup, read/write, listeners, rules
EXAMPLE
import { initializeApp } from 'firebase/app';
import {
getDatabase, ref, set, get, update, remove, push, child,
onValue, onChildAdded, onChildChanged, onChildRemoved,
query, orderByChild, orderByKey, orderByValue,
limitToFirst, limitToLast, startAt, endAt, equalTo,
serverTimestamp, increment, runTransaction,
goOffline, goOnline,
} from 'firebase/database';
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
// 1) Data model — JSON tree
// /users/u_42 = { name: 'Ada', email: 'a@x.com' }
// /posts/p_1 = { title: 'Hi', author: 'u_42', createdAt: 17... }
// /chats/room_1/messages/m_1 = { from: 'u_42', text: 'hi', ts: 17... }
// 2) Write
await set(ref(db, 'users/u_42'), {
name: 'Ada',
email: 'ada@example.com',
createdAt: serverTimestamp(),
});
// Push — auto-generated key
const newPostRef = push(ref(db, 'posts'));
await set(newPostRef, {
title: 'Hello',
author: 'u_42',
body: '...',
createdAt: serverTimestamp(),
});
console.log(newPostRef.key); // '-Nabc1234'
// Partial update
await update(ref(db, 'users/u_42'), {
name: 'Ada L.',
lastSeen: serverTimestamp(),
});
// Multi-path atomic update
await update(ref(db), {
'users/u_42/lastSeen': serverTimestamp(),
'sessions/sid_abc/userId': 'u_42',
'rooms/room_1/online/u_42': true,
});
// Remove
await remove(ref(db, 'users/u_42'));
await set(ref(db, 'users/u_42'), null); // equivalent — null deletes
// 3) Read (one-time)
const snap = await get(ref(db, 'users/u_42'));
if (snap.exists()) {
console.log(snap.val()); // { name: 'Ada', email: 'a@x.com' }
console.log(snap.key); // 'u_42'
}
// Multiple — use Promise.all
const [u1, u2] = await Promise.all([
get(ref(db, 'users/u_42')),
get(ref(db, 'users/u_99')),
]);
// 4) Real-time listener
const unsub = onValue(ref(db, 'users/u_42'), (snap) => {
setUser(snap.val());
});
// Later: unsub();
// Or .off() pattern (older)
import { off } from 'firebase/database';
off(ref(db, 'users/u_42'));
// 5) Child events
onChildAdded(ref(db, 'messages'), (snap) => {
appendMessage(snap.val());
});
onChildChanged(ref(db, 'messages'), (snap) => {
updateMessage(snap.key, snap.val());
});
onChildRemoved(ref(db, 'messages'), (snap) => {
removeMessage(snap.key);
});
// onChildAdded fires once per existing child + on every new addition
// 6) Queries — filtering + ordering
const recentMessages = query(
ref(db, 'rooms/room_1/messages'),
orderByChild('ts'),
limitToLast(50),
);
onChildAdded(recentMessages, (snap) => {
appendMessage(snap.val());
});
// Other query operators:
query(ref, orderByChild('age'), startAt(18), endAt(65));
query(ref, orderByChild('city'), equalTo('Sydney'));
query(ref, orderByKey(), startAt('u_4'));
query(ref, orderByValue(), limitToFirst(10));
// 7) Indexing — defined in security rules
{
"rules": {
"messages": {
".indexOn": ["ts", "author"]
}
}
}
// Without an index, queries fall back to scanning + warning in console
// 8) Transactions — atomic read + write
await runTransaction(ref(db, 'counters/orders'), (current) => {
return (current || 0) + 1;
});
// Conditional update
await runTransaction(ref(db, 'inventory/sku_A100'), (stock) => {
if (stock === null || stock <= 0) return; // abort
return stock - 1;
});
// runTransaction may retry several times under contention.
// 9) Server values
await set(ref(db, 'posts/p_1'), {
createdAt: serverTimestamp(),
likes: increment(1),
});
// Decrement
await update(ref(db, 'inventory/sku_A100'), { stock: increment(-1) });
// 10) Offline support
// Realtime DB caches data locally + queues writes when offline.
import { goOffline, goOnline } from 'firebase/database';
goOffline(db); // disable sync
goOnline(db); // re-enable
// 11) Connection state
onValue(ref(db, '.info/connected'), (snap) => {
console.log('connected:', snap.val()); // true / false
});
// 12) Security rules
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
},
"posts": {
".read": "auth != null",
"$postId": {
".write": "auth != null && (!data.exists() || data.child('author').val() == auth.uid)",
".validate": "newData.hasChildren(['title', 'author', 'createdAt'])",
"title": { ".validate": "newData.isString() && newData.val().length > 0" },
"author": { ".validate": "newData.val() == auth.uid" },
"createdAt": { ".validate": "newData.val() == now" }
}
},
"chats": {
"$roomId": {
".read": "root.child('rooms/' + $roomId + '/members').hasChild(auth.uid)",
".write": "root.child('rooms/' + $roomId + '/members').hasChild(auth.uid)"
}
}
}
}
// 13) onDisconnect — automatic cleanup
import { onDisconnect } from 'firebase/database';
const presenceRef = ref(db, `rooms/room_1/online/${userId}`);
onValue(ref(db, '.info/connected'), (snap) => {
if (snap.val()) {
set(presenceRef, { name: userName, since: serverTimestamp() });
onDisconnect(presenceRef).remove(); // auto-clean when client disconnects
}
});
// 14) Common patterns
// Chat with pagination
const MESSAGE_LIMIT = 50;
const messagesRef = query(
ref(db, `rooms/${roomId}/messages`),
orderByChild('ts'),
limitToLast(MESSAGE_LIMIT),
);
onChildAdded(messagesRef, (snap) => {
appendMessage({ id: snap.key, ...snap.val() });
});
// Send message
await push(ref(db, `rooms/${roomId}/messages`), {
from: currentUser.uid,
text: 'hello',
ts: serverTimestamp(),
});
// Online presence
const onlineRef = ref(db, `presence/${userId}`);
set(onlineRef, true);
onDisconnect(onlineRef).remove();
// Counter that increments atomically
await runTransaction(ref(db, 'counters/visits'), (n) => (n || 0) + 1);
// 15) Realtime DB vs Firestore — when to use which
// Realtime DB:
// - Cheaper per read for sustained streaming workloads
// - Simpler model (JSON tree)
// - Strong real-time sync (no per-document subscriptions)
// - Better for presence + chat + ephemeral state
// - Single region (latency!)
//
// Firestore:
// - Richer queries + indexes
// - Multi-region replication
// - Stronger consistency model
// - Better for structured data + complex queries
// - More expensive for many small reads
// 16) Performance + limits
// - 32 levels of depth
// - 1 GB per database / 100 simultaneous DB connections (Spark plan)
// - Max writes per second per database: ~1000 (sustained)
// - Multi-tab clients share connections via SharedWorker
// 17) Cost levers
// - Subscribe only to the data you need
// - Use orderByChild + limit to reduce data transferred
// - Unsubscribe when leaving a view
// - Avoid deep listeners (parent listener returns ALL children)
// - Denormalise smartly: profiles list + user details separately
// 18) Common bugs
// ❌ Subscribing to a high-traffic node without unsubscribe → memory leak + cost
// ❌ Forgetting indexes → queries slow + warn
// ❌ Server timestamps not respected because the rule didn't validate them
// ❌ Listening to a parent node that contains huge subtree → expensive
// ❌ runTransaction without idempotency → retries can cause double effects in your own code
// ❌ Writing arrays as objects with numeric keys → ordering gets confused
// 19) Best practices
// ✅ Set security rules BEFORE shipping
// ✅ Add .indexOn for every query field
// ✅ Use multi-path updates for atomic cross-node writes
// ✅ onDisconnect for presence
// ✅ runTransaction for counters
// ✅ Unsubscribe listeners on component unmount
// ✅ Denormalise based on read patterns
// ✅ For new projects: consider Firestore unless you specifically need RTDB's strengths
Why it matters
Realtime DB shines for chat, presence, and ephemeral state — cheap streaming sync + onDisconnect cleanup. For most new projects with structured data, prefer Firestore; RTDB only wins on sustained-streaming workloads.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { getDatabase, ref, set, onValue } from 'firebase/database';
const rt = getDatabase(app);
await set(ref(rt, 'users/1'), { name: 'Ada' });
onValue(ref(rt, 'users/1'), s => console.log(s.val()));
Try it Yourself »
Discussion
Loading…