Firestore Model
Database models for Firebase Firestore in Javascript. Create model classes with little configuration.
Highly inspired by Laravel Eloquent.
Table of contents
Install
npm install @guifcoelho/firestore-model
Configuration
Create a script to initilize Firebase when your app is booted.
//initFirebase.js
const firebase = require('firebase/app');
require('firebase/firestore');
if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: // The api key,
authDomain: // The auth domain,
projectId: // the project id
});
}
If you want to run Firebase as admin, first add the GOOGLE_APPLICATION_CREDENTIALS
variable into your .env file poiting to your Firebase service account json file, then:
const firebase = require('firebase-admin');
firebase.initializeApp({
credential: firebase.credential.applicationDefault()
});
process.firebase = firebase;
Finally, if you really need to run Firebase locally with emulators you will have to do an extra step and add the Firestore namespaces into the process
variable:
const localFirebase = require('@firebase/testing');
localFirebase.initializeTestApp({ projectId: 'project-id' });
const firebase = localFirebase.apps().find(app => app.options_.projectId == 'project-id');
process.firebase = firebase;
process.firestoreNamespaces = localFirebase.firestore;
Defining a model class
Create your models like this:
const Model = require('@guifcoelho/firestore-model');
module.exports = class DummyModel extends Model {
constructor(data){
const table = "dummy";
super(table, data, options);
}
}
Remember to instanciate the database
object and inject it to the class's super(...)
constructor.
You must define the table name, which is the same as your collection path name. For example, for a collection named users
just use const table = "users"
. If you want to refer to a collection inside another collection, just do const table = "collection1/other-colletion/my-collection"
.
If the table path makes reference to an unknown collection, such as const table = "users/user-id-1234/posts/post-id-123/comments"
where posts are added to each user, then you can use table params:
const table = 'users/$userId/posts/$postId/comments';
const tableParam = ['user-id-1234', 'post-id-123'];
Comment.createNew({title: 'This post is awesome!'}, tableParam);
Comment.whereAll( tableParam );
Comment.find('comment-id-123456', tableParam);
Comment.where('title', '==', 'This post is awesome!', tableParams);
If you want, you can define some schema for your data. FirestoreModel will look into your defined schema to determine if all attributes in the database have the right types and whether or not they are nullable. For example:
const Model = require('@guifcoelho/firestore-model');
module.exports = class DummyModel extends Model {
constructor(data){
const table = "users";
const RoleModel = require('./RoleModel.js');
const OtherModel = require('./OtherModel.js');
const options = {
schema: {
role: { type: RoleModel },
strings: { type: Array, arrayOf: 'string' },
name: { type: 'string', default: 'My name' },
last_name: { nullable: true },
my_date: { type: Date }
},
timestamps: false,
triggers: {
onCreate: data => OtherModel.createNew({title: `Created item '${data.id}'`}),
onUpdate: data => OtherModel.createNew({title: `Updated item '${data.new.id}'`}),
onWrite: data => OtherModel.createNew({title: `Wrote item '${data.id}'`}),
onDelete: data => OtherModel.createNew({title: `Deleted item '${data.id}'`})
}
};
super(table, data, options);
}
}
In that case, FirestoreModel will compare the data from the database with your schema.type
.
If your attribute is stored as a firebase.firestore.DocumentReference
you might pass the related FirestoreModel class as its type. If it checks out, a Query
object will be returned with the reference to the document. More about Query
later.
Also in the example above, the attribute name
is defined as string
(you can assign string
and number
for now). Finally, the last_name
attribute can be of any type and also null
. In the example, none of the other attributes are nullable.
If you want to require an attribute but not a type, just leave an empty property.
If you want to have a date attribute, just do options.schema.my_attribute.type = Date
. If your attribute is stored as firebase.firestore.Timestamp
, it will be converted as Date
.
With FirestoreModel you do not have to write timestamp fields to your data. Simply use options.timestamps = true
. Setting timestamps to true
will make FirestoreModel retrieve the document's timestamps and return the created_at
and updated_at
properties.
You can write event triggers (onCreate
, onUpdate
, onWrite
, and onDelete
) for the write, delete, update and create operations. You must write a function with a data
parameter that will receive the model's data right before the event is run. The onUpdate
trigger function will receive the data
parameter as {old, new}
.
See below the BaseModel constructor's properties:
- table:
string
- options:
object
- schema
- type: Tested for
string
,number
,Date
,Array
,Object
, instances ofBaseModel
or empty. Also, JS's primitive types and Firestore object's types - nullable:
boolean
- default: returns the defined value or function
- type: Tested for
- timestamps:
boolean
- triggers:
onCreate
(after successfulcreateNew()
function),onDelete
(after successfuldelete()
function),onUpdate
(after successfulupdate()
function) andonWrite
(after successfulsetById()
function)
- schema
Relations
You can set up relations with other tables using the hasOne(...)
and hasMany(...)
functions. Relations belongsTo(...)
, belongsToMany(...)
are not implemented yet.
hasOne
relation
The Add to your model class:
//Posts model class
item(){
const UniqueItemModel = require('./UniqueItemModel.js');
return this.hasOne(
UniqueItemModel /*The child class constructor*/,
"owner" /*The child's attribute pointing to the parent model*/,
"attribute_name" /*The parent model's attribute name to look for. The default is its DocumentReference, which is the recommended definition. Therefore, just leave it blank. */
);
}
hasMany
relation
The Add to your model class:
//Posts model class
comments(){
const CommentModel = require('./CommentModel.js');
return this.hasMany(
CommentModel /*The children class constructor*/,
"post" /*The children's attribute pointing to the parent model*/,
"attribute_name" /*The parent model's attribute name to look for. The default is its DocumentReference, which is the recommended definition. Therefore, just leave it blank. */
);
}