ORMOnFire is a powerful Firestore ORM.
Installation
Install package
# in backend
yarn add firebase firebase-admin firebase-tools
# in frontend
yarn add firebase
yarn add @typeheim/orm-on-fire
# or
npm -i @typeheim/orm-on-fire
Setup ORMOnFire driver:
FirebaseAdmin.initializeApp({
credential: FirebaseAdmin.credential.cert('my.key.json'),
databaseURL: "https://my-db.firebaseio.com",
})
OrmOnFire.driver = FirebaseAdmin.firestore()
Easy entity declaration
To define entity you need to use @Entity
or @Agregate
decorators for simple and nested collections. Note both
decorators will transform class name to kebab case - lowercase and split words with hyphens, like UserFiles
<
=> user-files
.
Then, each document field must be decorated with @Field
, @MapField
, @CreatedDateField
, @UpdatedDateField
or @DocRef
(for document references) decorators. Sub-collections can be referenced by @CollectionRef
decorator.
import {
Agregate,
Entity,
Collection,
CollectionRef,
ID,
Field,
CreatedDateField,
UpdatedDateField,
MapField
} from '@typeheim/orm-on-fire'
import { CreatedDateField } from './Entity'
@Agregate()
export class User {
@ID() id: string
@Field() firstName: string
@Field() lastName: string
@Field() status: string
@CollectionRef(UserFile) files: Collection<UserFile>
}
@Entity({ collection: 'user-files' })
export class UserFile {
@ID() id: string
@Field() name: string
@MapField() properties: FileProperties
@CreatedDateField() createdAt: Date
@UpdatedDateField() createdAt: Date
}
class FileProperties {
type: "image" | "doc"
}
Simple data fetching
import { Collection } from '@typeheim/orm-on-fire'
let markus = await Collection.of(User).one('markus').get()
Collection.of(User).one('tom').get().subscribe((tom: User) => {
tom.files.forEach((file: UserFile) => {
})
})
Powerful filtering
Using firestore operators
import { Collection } from '@typeheim/orm-on-fire'
const UsersCollection = Collection.of(User)
let activeUsers = await UsersCollection.all().filter(user => user.status.equal('active')).get()
let notActiveUsers = await UsersCollection.all().filter(user => user.status.notEqual('active')).get()
let adultUsers = await UsersCollection.all().filter(user => user.age.greaterThan(18)).get()
Test index search
To use text index search you first need to add text index hook Firebase function to your Firebase functions list for
each colelction you want to be idndexed
import { TextIndex } from '@typeheim/orm-on-fire'
import * as functions from 'firebase-functions'
import * as FirebaseAdmin from 'firebase-admin'
FirebaseAdmin.initializeApp()
export const generateUserIndex = TextIndex(functions, FirebaseAdmin).forCollection('users')
.fields(['name', 'text'])
.buildTrigger()
Official functions deployment
guide: Get started: write, test, and deploy your first functions
Once you deploy hooks, you can use index search as below:
let usersStartsWithAlex = await UsersCollection.all().useIndex(user => user.firstName.startsWith('Alex')).get()
let usersEndsWithLex = await UsersCollection.all().useIndex(user => user.firstName.endsWith('lex')).get()
NOTE: for now text index won't work with collection group queries. Support coming in next releases.
Filter scopes:
Commonly used filer conditions can be organized in named filter scopes for easy code reuse:
class UserScope {
static active() {
return (user: EntityFilter<User>) => {
user.status.equal(1)
}
}
}
let activeUsers = await UsersCollection.all().filter(UserScope.active()).get()
Sub-collection queries:
For nested collections you don't need to fetch each document separately and can access required collection under
specific document ID:
let userFiles = await UsersCollection.one('userId').collecction(UserFile).filter(UserFile.pdf()).get()
Group collection queries:
ORMOnFire support easy declaration
of collection groups the same way
as for regular collections.
let attechments = await Collection.groupOf(Attachment).all().filter(Attachment.file()).get()