agentsclimarketplace

Firebase realtime database

Skill DentVega/firebase-agent-skills/skills/firebase-realtime-database

Community Firebase agent skills for AI coding assistants — Expo / React Native focus

Install
npx -y skills add DentVega/firebase-agent-skills --skill firebase-realtime-database

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Sets up and uses Firebase Realtime Database (RTDB) — provisioning, security rules in the JSON expression format, presence detection with onDisconnect, low-latency chat patterns, and shallow vs. deep queries. Use when the user needs presence (online/offline), real-time chat with minimal latency, collaborative cursor state, or any data that's tree-shaped and changes by the millisecond. For document-oriented or large-scale data, prefer Firestore.

SKILL.md

7.7 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

Firebase Realtime Database

Minimum viable example

import database from "@react-native-firebase/database";

// Write
await database().ref(`messages/${roomId}`).push({
  text: "hi",
  uid: auth().currentUser?.uid,
  ts: database.ServerValue.TIMESTAMP,
});

// Listen
const ref = database().ref(`messages/${roomId}`).limitToLast(50);
const unsub = ref.on("value", (snap) => {
  const msgs = snap.val() ? Object.entries(snap.val()) : [];
  setMessages(msgs);
});
// cleanup: ref.off("value", unsub);

RTDB vs. Firestore — pick one

Use RTDBUse Firestore
Presence (online users)Almost everything else
Sub-100ms collaborative cursorsDocument-oriented data
Append-only logs at high frequencyComplex queries with where + orderBy
Simple tree shapesCross-collection joins
Sub-1ms read latency requiredStrong schema validation

RTDB charges by bytes downloaded and concurrent connections. Firestore charges by operations. For chat with 100k messages, RTDB tends to be cheaper; for a CRUD app with mostly cold data, Firestore wins.

In a single app, use both: Firestore for the main data model, RTDB for presence and ephemeral live state.

1. Initialize

npx -y firebase-tools@latest init database

Generates database.rules.json. Pick a location at creation; cannot be changed.

{
  "rules": {
    ".read": false,
    ".write": false
  }
}

Default-deny. Open up paths explicitly.

2. Security rules — different from Firestore

Rules are a JSON tree mirroring your data tree, with .read, .write, .validate expressions at each node:

{
  "rules": {
    "users": {
      "$uid": {
        ".read":  "auth != null && auth.uid == $uid",
        ".write": "auth != null && auth.uid == $uid"
      }
    },
    "messages": {
      "$roomId": {
        ".read":  "auth != null",
        ".indexOn": ["ts"],
        "$msgId": {
          ".write": "auth != null && (!data.exists() || data.child('uid').val() == auth.uid)",
          ".validate": "newData.hasChildren(['text', 'uid', 'ts'])
                        && newData.child('text').isString()
                        && newData.child('text').val().length < 500
                        && newData.child('uid').val() == auth.uid
                        && newData.child('ts').val() == now"
        }
      }
    },
    "presence": {
      "$uid": {
        ".read":  "auth != null",
        ".write": "auth != null && auth.uid == $uid"
      }
    }
  }
}

Key differences from Firestore:

  • No request.auth.uid — it's auth.uid
  • data is existing, newData is the proposed value
  • .read / .write cascade down — granting at a parent path grants for everything under it (in Firestore, rules don't cascade)
  • .indexOn required to use orderByChild on that key, similar to Firestore composite indexes but per-path

Deploy:

npx -y firebase-tools@latest deploy --only database

3. Presence detection (the killer feature)

RTDB's onDisconnect runs server-side when the client connection drops — even on crash or network failure. Firestore has no equivalent.

import database from "@react-native-firebase/database";
import auth from "@react-native-firebase/auth";

auth().onAuthStateChanged((user) => {
  if (!user) return;
  const presenceRef = database().ref(`presence/${user.uid}`);
  const connectedRef = database().ref(".info/connected");

  connectedRef.on("value", (snap) => {
    if (snap.val() === false) return;

    // Register: when this client disconnects (gracefully or not), clear presence
    presenceRef.onDisconnect().remove().then(() => {
      presenceRef.set({
        state: "online",
        lastChanged: database.ServerValue.TIMESTAMP,
      });
    });
  });
});

The .info/connected node tracks the actual connection state. The onDisconnect().remove() is registered server-side and fires when the connection drops. Result: /presence/{uid} reliably reflects who's online without polling.

For "last seen" in addition to online state:

presenceRef.onDisconnect().set({
  state: "offline",
  lastChanged: database.ServerValue.TIMESTAMP,
});

4. Querying

RTDB queries are limited compared to Firestore — you can sort by one field and filter by one range. No compound queries.

// Recent messages
database().ref(`messages/${roomId}`)
  .orderByChild("ts")
  .limitToLast(50)
  .on("value", handler);

// Messages since timestamp
database().ref(`messages/${roomId}`)
  .orderByChild("ts")
  .startAt(sinceTs)
  .on("value", handler);

For complex filtering, fan-out write to denormalized indexes:

// On message create, also write to /user-messages/{uid}/{msgId}
const updates = {};
updates[`messages/${roomId}/${msgId}`] = msg;
updates[`user-messages/${uid}/${msgId}`] = msg;
await database().ref().update(updates);

Atomic multi-path update — both or neither.

5. Cost — minimize bytes downloaded

RTDB downloads the entire subtree you reference. ref('/messages').on('value', ...) downloads every message in every room — terrible. Always scope to the smallest path:

  • ref('/messages/room-123').limitToLast(50)
  • ref('/').on('value', ...)

Use .indexOn so range queries don't scan client-side.

For data that should only fetch once, use .once('value') instead of .on('value') to avoid the persistent connection cost.

6. Client SDK setup

Web

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

const db = getDatabase();
await push(ref(db, `messages/${roomId}`), { text, uid, ts: serverTimestamp() });

Expo / React Native

npx expo install @react-native-firebase/database
import database from "@react-native-firebase/database";

await database().ref(`messages/${roomId}`).push({
  text, uid, ts: database.ServerValue.TIMESTAMP,
});

7. Emulator

npx -y firebase-tools@latest emulators:start --only database

Connect:

// Web
import { connectDatabaseEmulator } from "firebase/database";
if (process.env.NODE_ENV === "development") {
  connectDatabaseEmulator(db, "127.0.0.1", 9000);
}

// RN
database().useEmulator("127.0.0.1", 9000);

8. Common mistakes

  • Listening at the root. .on('value') on / downloads the entire database. Always scope to the smallest path.
  • No .indexOn for orderByChild. Works locally because the emulator does in-memory sort; in production, rules reject the query.
  • Forgetting to call .off() on unmount. Listeners leak; bandwidth + connection bills grow.
  • Trying to do compound queries. RTDB can sort by one field, filter by one range. Use denormalized fan-out indexes for everything else.
  • Storing growing arrays. RTDB has no array type — what looks like an array is {0: x, 1: y, ...}. Use push() for auto-IDs instead of array indexes.
  • Forgetting onDisconnect doesn't fire on the client. It's registered server-side and runs after the connection drops. Test it by killing the device's network, not by closing the app gracefully.
  • Mixing RTDB and Firestore listeners on the same data. Pick one source of truth per piece of state.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most databases sql skills give in ~1.8k tokens

Counted across 589 of the 662 authors here whose files we hold, read 2026-08-07

  • Use parameterized queriesin 37 of 589, across 34 files
  • Use timestamptz for timestampsin 30 of 589, across 14 files
  • Index foreign keysin 29 of 589, across 18 files
  • Create indexes concurrentlyin 29 of 589, across 24 files
  • Use numeric type for moneyin 25 of 589, across 8 files
  • Use cursor pagination instead of offsetin 24 of 589, across 17 files
  • Select only required columnsin 24 of 589, across 20 files
  • Add indexes manually on foreign key columnsin 22 of 589, across 12 files
  • Normalize to third normal formin 19 of 589, across 10 files
  • Configure connection poolingin 19 of 589, across 17 files
  • Put equality columns before range columns in indexesin 18 of 589, across 10 files
  • Read individual rule files for detailed explanationsin 18 of 589, across 4 files

Said here and by no other author read

  • use firebase realtime database for low-latency ephemeral data
  • default-deny all database paths
  • open paths explicitly in security rules
  • use onDisconnect for presence detection
  • scope reads to the smallest path
  • define indexOn for range queries

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 326,984. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.