iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…