Firebase Realtime Database Modeler
Firebase Database Modeler is a TypeScript-first library designed to enhance development with the Firebase Realtime Database by providing a structured, strongly-typed modeling layer. Currently at version 2.8.0, it aims to abstract the complexities of database path management and data serialization through a declarative model definition. It offers robust IntelliSense support, automatically converting between the defined model schema and the actual database structure. The library supports integration with `firebase`, `firebase-admin`, and `react-native-firebase`, making it versatile for various JavaScript environments. While a strict release cadence isn't published, the developer indicates active use in a real project, implying ongoing maintenance and feature evolution. Its key differentiation lies in bringing advanced type safety and a clear object-oriented approach to Firebase Realtime Database interactions, simplifying complex data structures and reducing common runtime errors associated with schema mismatches.
Common errors
-
TS2339: Property 'database' does not exist on type 'typeof firebase'.
cause Incorrect or incomplete Firebase SDK import for Realtime Database, often happening when using modular Firebase v9+ without the compat layer or specific database service imports.fixEnsure you have correctly imported the Realtime Database service. For Firebase v8 or compat mode: `import firebase from 'firebase/compat/app'; import 'firebase/compat/database';`. For modular v9+: `import { getDatabase } from 'firebase/database';` and initialize the database instance from `getDatabase(app)`. -
TS2345: Argument of type '{ name: string; rating: number; open: boolean; users: { [x: string]: { name: string; }; }; }' is not assignable to parameter of type 'StoreModel'.cause Attempting to call `_set()` with an object that omits properties defined as non-nullable in the model for that path. `_set()` expects a complete object matching the model's required properties.fixEither provide all required properties for the `_set()` call or, if you intend to perform a partial update, target a more specific child node in your model (e.g., `stores.$storeId.name._set(newName, storeId)`). Alternatively, adjust your model definition to mark properties as optional (`_<Type | null>('dbKey')`) if they are not always present. -
TypeError: Cannot read properties of undefined (reading '_set')
cause This typically means the underlying Firebase Realtime Database reference is `undefined`, often because `modelerSetDefaultDatabase` was not called or an invalid database instance was passed to `_root` or `._ref()`.fixVerify that `modelerSetDefaultDatabase(firebase.database());` has been executed before any model operations, or that the `database` argument was correctly passed to your `_root()` model definition.
Warnings
- gotcha Operations on a model node will fail if a default Firebase Realtime Database instance has not been set or explicitly passed to the root node or `_ref()` function.
- gotcha When using `_set()` on a model node, all properties defined in the model for that specific path segment are considered required, unless explicitly marked with `| null` in the model definition. Attempting a partial update with `_set()` will result in a TypeScript error or data loss for omitted fields.
- gotcha The library allows property key aliasing where the model property name differs from the actual database key (e.g., `name: _<string>('n')`). This is powerful but can be a source of confusion if not carefully managed, as direct database path manipulation will use the database key, not the model property name.
- gotcha Variable path segments, defined with `_$()`, require the actual value for that segment to be passed as the last argument to operations like `_set()`, `_onceVal()`, `_ref()`, etc. (e.g., `stores.$storeId._set(data, storeId)`). Forgetting to pass the variable value will lead to incorrect paths.
Install
-
npm install firebase-database-modeler -
yarn add firebase-database-modeler -
pnpm add firebase-database-modeler
Imports
- modelerSetDefaultDatabase
const { modelerSetDefaultDatabase } = require('firebase-database-modeler');import { modelerSetDefaultDatabase } from 'firebase-database-modeler'; - _, _$
import ModelNodes from 'firebase-database-modeler';
import { _, _$ } from 'firebase-database-modeler'; - _root
import { root } from 'firebase-database-modeler';import { _root } from 'firebase-database-modeler';
Quickstart
import { _, _$, _root, modelerSetDefaultDatabase } from 'firebase-database-modeler';
import firebase from 'firebase/compat/app';
import 'firebase/compat/database';
// Initialize Firebase (replace with your actual config)
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY ?? 'YOUR_API_KEY',
authDomain: process.env.FIREBASE_AUTH_DOMAIN ?? 'YOUR_AUTH_DOMAIN',
projectId: process.env.FIREBASE_PROJECT_ID ?? 'YOUR_PROJECT_ID',
storageBucket: process.env.FIREBASE_STORAGE_BUCKET ?? 'YOUR_STORAGE_BUCKET',
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID ?? 'YOUR_MESSAGING_SENDER_ID',
appId: process.env.FIREBASE_APP_ID ?? 'YOUR_APP_ID',
databaseURL: process.env.FIREBASE_DATABASE_URL ?? 'YOUR_DATABASE_URL'
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
const database = firebase.database();
modelerSetDefaultDatabase(database);
const stores = _('stores', {
$storeId: _$1({
name: _<string>('n'), // DB key 'n' for model property 'name'
rating: _<number>('rating'),
open: _<boolean>('open'),
optionalProp: _<number | null>('oP'), // Optional property
users: _('users', {
$userId: _$1({
name: _<string>('name')
})
})
})
});
const root = _root({
stores
});
async function createStore(storeId: string, userId: string, userName: string) {
// All non-null model properties for the current path must be provided.
await stores.$storeId._set({
name: 'Cool Store',
rating: 4.2,
open: true,
users: {
[userId]: {
name: userName
}
}
}, storeId);
console.log(`Store ${storeId} created.`);
}
async function setStoreName(storeId: string, newName: string) {
// To update a single field, target its specific path.
await stores.$storeId.name._set(newName, storeId);
console.log(`Store ${storeId} name updated to ${newName}.`);
}
async function getStore(storeId: string) {
const store = await stores.$storeId._onceVal('value', storeId);
console.log(`Fetched store ${storeId}:`, store);
return store;
}
// Example usage
(async () => {
const myStoreId = 'store_abc';
const myUserId = 'user_123';
await createStore(myStoreId, myUserId, 'Alice');
await setStoreName(myStoreId, 'Super Cool Store');
await getStore(myStoreId);
})();