Firebase realtime database
Skill DentVega/firebase-agent-skills/skills/firebase-realtime-database
Community Firebase agent skills for AI coding assistants — Expo / React Native focus
npx -y skills add DentVega/firebase-agent-skills --skill firebase-realtime-databaseAssembled 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 RTDB | Use Firestore |
|---|---|
| Presence (online users) | Almost everything else |
| Sub-100ms collaborative cursors | Document-oriented data |
| Append-only logs at high frequency | Complex queries with where + orderBy |
| Simple tree shapes | Cross-collection joins |
| Sub-1ms read latency required | Strong 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'sauth.uid datais existing,newDatais the proposed value.read/.writecascade down — granting at a parent path grants for everything under it (in Firestore, rules don't cascade).indexOnrequired to useorderByChildon 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
.indexOnfororderByChild. 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, ...}. Usepush()for auto-IDs instead of array indexes. - Forgetting
onDisconnectdoesn'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.