Elegant object modeling for Google Cloud Firestore.
Inspired by mongoose and datalize.
Requires firebase-admin
package.
npm install --save firestore-schema-validator
// UserModel.js
const { Model, schema, field } = require('firestore-schema-validator')
const userSchema = schema({
firstName: field('First Name')
.string()
.trim(),
lastName: field('Last Name')
.string()
.trim(),
email: field('Email Address')
.string()
.email(),
})
class UserModel extends Model {
// Path to Cloud Firestore collection.
static get _collectionPath() {
return 'users'
}
// Model Schema.
static get _schema() {
return userSchema
}
}
// UserModel.js
const { Model, schema, field } = require('firestore-schema-validator')
const userSchema = schema({
firstName: field('First Name')
.string()
.trim(),
lastName: field('Last Name')
.string()
.trim(),
password: field('Password')
.string()
.match(/[A-Z]/, '%s must contain an uppercase letter.')
.match(/[a-z]/, '%s must contain a lowercase letter.')
.match(/[0-9]/, '%s must contain a digit.')
.minLength(8),
email: field('Email Address')
.string()
.email(),
emailVerificationCode: field('Email Verification Code')
.string()
.nullable(),
birthDate: field('Birth Date')
.date('YYYY-MM-DD')
.before(
moment()
.subtract(13, 'years')
.toISOString(),
'You must be at least 13 years old.',
),
options: field('Options')
.objectOf({
lang: field('Language')
.oneOf([
'en-US',
'pl-PL'
])
.default('en-US'),
})
})
class UserModel extends Model {
static get _collectionPath() {
return 'users'
}
static get _schema() {
return userSchema
}
// You can define additional methods...
static async getByEmail(email) {
return await this.getBy('email', email)
}
// ... or getters.
get isEmailVerified() {
return Boolean(this._data.emailVerificationCode)
}
get fullName() {
return `${this._data.firstName} ${this._data.lastName}`
}
// this.toJSON() by default returns this._data,
// but you might want to display it differently
// (eg. don't show password in responses,
// combine firstName and lastName into fullName, etc.)
toJSON() {
return {
id: this._id, // ID of Document stored in Cloud Firestore
createdAt: this._createdAt, // ISO String format date of Document's creation.
updatedAt: this._updatedAt, // ISO String format date of Document's last update.
fullName: this.fullName,
email: this.email,
isEmailVerified: this.isEmailVerified,
}
}
}
// Fired when new user is successfully created and stored.
UserModel.on('created', async (user) => {
// eg. send Welcome Email to User
})
// Fired when user is successfully updated and stored.
UserModel.on('updated', async (user) => {
// eg. log info to console
})
// Fired when user is succsessfully deleted.
UserModel.on('deleted', async (user) => {
// eg. delete photos uploaded by User
})
// Fired during user.validate() if user.email has changed,
// but *before* validating the data.
UserModel.prehook('email', (data, user) => {
// eg. set emailVerificationCode
})
// Fired during user.validate() if user.email has changed,
// but *after* validating the data.
UserModel.posthook('email', (data, user) => {
// eg. send Email Verification Email to User
})
UserModel.posthook('password', (data, user) => {
// eg. hash password to store it securely
})
const admin = require('firebase-admin')
const User = require('../UserModel.js')
// Initialize Firebase
admin.initailizeApp({
// config
})
const user = await User.create({
firstName: 'Jon',
lastName: 'Doe',
email: 'jon.doe@example.com',
password: 'J0nD03!@#',
birthDate: '1990-01-10',
}) // => instance of UserModel
console.log(user.toJSON()) // =>
// {
// id: 'x22sSpmaJek0CYS9KTsI'.
// createdAt: '2019-06-14T15:46:55.108Z',
// updatedAt: null,
// fullName: 'Jon Doe',
// email: 'jon.doe@example.com',
// isEmailVerified: false,
// }
user.firstName = 'J'
user.foo = 'bar' // Won't be stored as it's not defined in UserModel._schema
await user.save() // => instance of UserModel
console.log(user.toJSON()) // =>
// {
// id: 'x22sSpmaJek0CYS9KTsI'.
// createdAt: '2019-06-14T15:46:55.108Z',
// updatedAt: null,
// fullName: 'J Doe',
// email: 'jon.doe@example.com',
// isEmailVerified: false,
// }
const fetchedUser = await UserModel.getByEmail('jon.doe@example.com') // => instance of UserModel
console.log(fetchedUser.toJSON()) // =>
// {
// id: 'x22sSpmaJek0CYS9KTsI'.
// createdAt: '2019-06-14T15:46:55.108Z',
// updatedAt: null,
// fullName: 'J Doe',
// email: 'jon.doe@example.com',
// isEmailVerified: false,
// }
await fetchedUser.delete()
const nonExistingUser = await UserModel.getByEmail('jon.doe@example.com') // => null
field.arrayOf(fieldOrSchema)
and field.objectOf(objectOfFieldsOrSchema)
accept instances of Schema as an argument, so you can reuse repeatable schemas:
const { schema, field } = require('firestore-schema-validator')
const simplifiedAddressSchema = schema({
street: field('Street')
.string()
.trim(),
countryCode: field('Country')
.oneOf([
'US',
'CA',
]),
zipCode: field('ZIP Code')
.string()
.trim(),
})
const userSchema = schema({
firstName: field('First Name')
.string()
.trim(),
lastName: field('Last Name')
.string()
.trim(),
mailingAddress: field('Mailing Address')
.objectOf(simplifiedAddressSchema),
})
const companySchema = schema({
name: field('Company Name')
.string()
.trim(),
locations: field('Locations')
.arrayOf(simplifiedAddressSchema),
})
Field.prototype.unique()
that checks if the value provided to field is unique in the collection.