# XMongoModel
xpress-mongo's parent model class
is XMongoModel
(opens new window)
# Creating a Model
A xpress-mongo Model is simply a Class that extends XmongoModel
and is linked to a collection.
# Basic Model
Note: A model will not be able to make database calls until it is linked to a collection.
A Model can also be linked to a collection by extending the auto-generated Model returned by
calling connection.model("collection")
. See Advanced Model example below.
# Advanced Model
# Static Methods
# native()
XMongoModel.native()
returns mongodb native collection instance for you to run any mongodb native queries.
Adding a document using mongodb native
await Users.native().insertOne({
email: 'john@doe.com',
firstName: 'John',
lastName: 'Doe'
});
# thisCollection() - Deprecated
Use .native() instead - v0.0.40
# on()
XMongoModel.on(event: string, run: function)
is used to listen for create, created, update, watch or deleted
events. See Model Events.
Model.on('update', modelInstance => {
// Make changes
})
# new() - async
XMongoModel.new(data: {}, save: boolean = true)
is used to add a new document to the collection.
await Users.new({
email: 'john@doe.com',
firstName: 'John',
lastName: 'Doe'
});
The second argument save
if set to false returns model instance without saving to the database.
const newUser = await Users.new({
email: 'john@doe.com',
firstName: 'John',
lastName: 'Doe'
}, false);
newUsers.set('age', 18);
await newUsers.save();
The above also has a function that makes it easier to understand. See XMongoModel.make()
below.
# make()
XMongoModel.make(data: {})
creates a model instance with the data provided. this instance does not have any link to
the database unless the save
method is called.
const newUser = Users.make({
email: 'john@doe.com',
firstName: 'John',
lastName: 'Doe'
});
newUsers.set('age', 18);
await newUsers.save();
# use()
XMongoModel.use(data: {})
creates and returns a model instance using the data provided. Mostly used to convert an
object to model instance.
Let's assume you have a raw data returned from the native client findOne
query, it can be converted to a model
instance using use()
const rawData = await Users.native().findOne({});
const user = Users.use(rawData);
// Updates same document
await user.update({
age: 20
})
# fromArray()
version >= **1.1.1** does not allow passing a query function to `.fromArray` anymore. Use `.fromQuery`
instead.
XMongoModel.fromArray(list: any[], mutate: boolean)
is used to convert arrays of raw data to an array of instance
models.
For example when using XMongoModel.find()
the results returned is a clean array not an array of model instances.
You can convert that result using fromArray([])
const rawList = await Users.find({});
const users = Users.fromArray(rawList);
// Updates the first document
await users[0].update({
age: 20
});
Note: if second argument i.e. mutate
is true, the array passed will be mutated. For example
const rawList = await Users.find({});
Users.fromArray(rawList, true); // users will be mutated
// Updates the first document
await rawList[0].update({
age: 20
});
# fromQuery() - async
The fromQuery
method unlike fromArray
runs the query for you and returns an array of models. It expects a query function as the first argument.
For Example
const users = await Users.fromQuery(native => native.find())
// `native` is same as `Model.native()` i.e Mongodb native query builder.
// Now you can access model instance functions
if (users.length) {
console.log(users[0].fullName()) // John Doe
}
This also makes it easy to convert native aggregate results.
const users = await Users.fromQuery(native => native.aggregate([]))
if (users.length) {
console.log(users[0].fullName()) // John Doe
}
NOTE: fromQuery
only supports queries that returns an array. E.g find
, aggregate
e.t.c
# id()
XMongoModel.id(id: string)
converts the string passed to a mongodb ObjectID
const userId = Users.id('5f43e78c9da24b1444d7c998')
// returns ObjectId("5f43e78c9da24b1444d7c998")
# isValidId()
XMongoModel.isValidId(id: any)
verifies if id passed as argument is a valid mongodb ObjectID.
if (Users.isValidId('5f43e78c9da24b1444d7c998'))
return true;
# find() - async
XMongoModel.find(query: {}, options?: {}, raw?: false)
This method is used to find documents in a collection and does not return results as model instances
// Find all users with `age` greater than or equals to `18`
let users = await Users.find({
age: {$gte: 18}
});
// Convert results to model instances
users = Users.fromArray(users);
options?
accepts mongodb native FindOneOptions
E.g sort by email and return only {email, age}
const users = await Users.find({
age: {$gte: 18}
}, {
sort: {email: 1},
projection: {email: 1, age: 1}
});
raw?
is set to false by default but when set to true, the native find
query builder is returned instead of query
results.
const users = await Users.find({
age: {$gte: 18}
}, true).limit(1).skip(10).toArray();
# findOne() - async
XMongoModel.findOne(query: {}, options?: {}, raw?: false)
same as find()
but returns only one document as model
instance.
const john = await Users.findOne({firstName: 'John'});
# findById() - async
XMongoModel.findById(_id: any, options?: {})
is used to find one document using the _id
const user = await Users.findById('5f43e78c9da24b1444d7c998')
# sum() - async
XmongoModel.sum(field: string, match?: {})
is used to sum up a field, with option to filter results using match
.
const worth = await Transactions.sum('balance');
const income = await Transactions.sum('balance', {type: 'credit'});
// `worth` will be the sum of all transactions
// `income` will be the sum of all credit transactions
# sumMany() - async
XmongoModel.sumMany(fields: string[] | Record<string, string>, match?: {})
sums up multiple fields.
await Transactions.sumMany(['balance', 'owing']);
// { balance: number, owing: number}
await Transactions.sumMany({
totalBalance: 'balance',
totalDebt: 'owing'
});
// { totalBalance: number, totalDebt: number}
# count() - async
XMongoModel.count(query?: {})
is used to count documents that matches the query passed. if no query is passed, it
counts all elements.
const numberOfUsers = await Users.count();
const numberOfAdults = await Users.count({
age: {$gte: 18}
})
# countAggregate() - async
XmongoModel.countAggregate(query: [], options?: {})
is same as count()
but used to count results from an aggregate
query.
# paginate() - async
XmongoModel.paginate(page: number, perPage: number, query: {}, options?: {})
is used to paginate results
// Assuming this is a controller action
async function getAllUsers(req, res) {
// Get page from
const page = req.query.page || 1;
const perPage = 30;
// Pagination of all users with age >= 18, sort by firstName
const users = await Users.paginate(page, perPage, {
age: {$gte: 18}
}, {
sort: {firstName: 1}
});
// Return response
return res.json({users});
};
The result
{
"total": 1,
"perPage": 20,
"page": 1,
"lastPage": 1,
"data": [
{
"_id": "5f43e78c9da24b1444d7c998",
"email": "john@doe.com",
"firstName": "John",
"lastName": "Doe",
"age": 18
}
]
}
# paginateAggregate() - async
XmongoModel.paginateAggregate(page: number, perPage: number, query: {}, options?: {})
is same
with XMongoModel.paginate
but works with aggregation query.
// Assuming this is a controller action
async function getAllUsers(req, res) {
const page = req.query.page || 1;
const perPage = 30;
// Pagination of all users with age >= 18, sort by firstName
const users = await Users.paginateAggregate(
page,
perPage,
[/* Pagination Query */],
{/* Collection Options */}
);
// Return response
return res.json({users});
};
# Instance Methods
# changes()
this.changes()
shows the difference between original model data and current data. so if you make any changes it is
returned when calling this method
/**
* Assuming Data is {
* _id: 5f43e78c9da24b1444d7c998,
* firstName: 'John',
* lastName: 'Doe',
* }
*/
const user = await Users.findById('5f43e78c9da24b1444d7c998');
user.set('lastName', 'Joe')
console.log(user.changes());
// => {lastName: 'Joe'}
# delete() - async
this.delete()
deletes the current document from the collection.
const john = await Users.findOne({firstName: 'John'});
if (john) await john.delete();
# get()
this.get(key: string, $default: any)
gets field value from the current document.
const john = await Users.findOne({firstName: 'John'});
john.get('firstName');
john.get('lastName');
// Set defualt value if key is not found.
john.get('verified', false);
# has()
this.has(key: string, value: any | (() => any) = undefined): boolean
checks if a field exists or if its value matches
the specified value.
If value
is function, it will be called and its return value will be used to Compare
against the fields value.
# hasChanges()
this.hasChanges(): boolean
checks if the current document has unsaved changes
const user = await User.findOne({email: "john@doe.com"})
user.hasChanges() // false
// Change Email
user.set("email", "john@doe1.com");
user.hasChanges() // true
# hasChanged()
this.hasChanged(keys: string | string[]): boolean
checks if a field has unsaved changes.
// Data from update form
const body = {
email: "john@doe1.com",
firstName: "John",
lastName: "Doe"
}
// Fetch User
const user = await User.findOne({email: "john@doe.com"})
// Update data
user.set(body);
if (user.hasChanged("email")) {
// validate new email
}
// Then save
await user.save()
# id()
this.id()
returns the current _id of the document
# idEqualTo()
this.idEqualTo(to: any, key: '_id')
is used to Compare model id with a string or ObjectId type field.
/**
* Data: {
* _id: 5f43e78c9da24b1444d7c998,
* firstName: 'John',
* lastName: 'Doe',
* userId: 5e0ee7f56a5df504e669f876
* }
*/
const john = await Users.findOne({firstName: 'John'});
console.log(john.idEqualTo('not-object-id-string'));
// false
console.log(john.idEqualTo('5f43e78c9da24b1444d7c998'));
// true
/**
* When key is specified it compares the id provided
* with the value of the key specified.
*/
console.log(john.idEqualTo('5f43e78c9da24b1444d7c998', 'userId'));
// false
console.log(john.idEqualTo('5e0ee7f56a5df504e669f876', 'userId'));
// True
# pushToArray()
this.pushToArray(key: string, value: any, strict?: boolean = false)
is used to push an item to an array in the current
data held by the model.
/**
* Data: {
* _id: 5f43e78c9da24b1444d7c998,
* firstName: 'John',
* lastName: 'Doe',
* userId: 5e0ee7f56a5df504e669f876,
* about: {
* brothers: ['Paul', 'Peter'],
* hobbies: ['Eat', 'Code', 'Sleep']
* }
* }
*/
const user = await User.findById('5f43e78c9da24b1444d7c998');
// Push hobby 'BasketBall` to hobbies array
user.pushToArray('about.hobbies', 'Basketball')
await user.save();
Note: if the key requested for does not exist or is not an array, an error is throwed.
# save() - async
this.save()
updates a document if model has _id in its data object. if _id is missing it creates the document
and saves the new _id to the model instance. Can be used when creating or when updating a document.
# Creating
const user = new User().set({
firstName: 'Paul',
lastName: 'Dean'
});
await user.save();
user.save()
will create a new document because the model data has no _id
# Updating
const user = await User.findById('5f43e78c9da24b1444d7c998');
user.set('about.address', 'no 44 billboard avenue.')
await user.save();
user.save()
will update the document using its _id
Note The save()
method does not return a model instance or document. It returns the default mongodb
insert/update operation result.
# saveAndReturn() - async
This is similar to save but returns the model instance.
const user = await new User().set({name: 'John'}).saveAndReturn();
# set()
this.set(key: string | {}, value: any)
is used to add or update fields and value to the model's data. This data does
not save to database until save()
is called.
const user = new User();
user.set('firstName', 'John')
user.set('lastName', 'Doe')
await user.save();
if the first argument is an object and value===undefined
each key and value in the object will be set as fields and
value respectively.
The above code can also be written like this:
const user = new User();
user.set({
firstName: 'John',
lastName: 'Doe'
})
await user.save();
# toCollection()
this.toCollection()
returns an object-collection (opens new window) instance of models
data, giving you all the functions of the object-collection library.
Below are some examples.
/**
* Data: {
* _id: 5f43e78c9da24b1444d7c998,
* firstName: 'John',
* lastName: 'Doe',
* userId: 5e0ee7f56a5df504e669f876,
* about: {
* brothers: ['Paul', 'Peter'],
* hobbies: ['Eat', 'Code', 'Sleep']
* }
* }
*/
const user = await User.findById('5f43e78c9da24b1444d7c998');
const userCollection = user.toCollection();
userCollection.pick(['firstName', 'lastName']);
// => {firstName: 'John', lastName: 'Doe'}
// add `address` in about object
userCollection.path('about').set('address', 'No 10, some street avenue.')
// Read `address`
userCollection.get('about.address')
// => No 10, some street avenue.
# toJson()
this.tojson(replacer?: any, spacing?: number)
simply returns the data of the model as json string. It
uses: JSON.stringify()
in the background.
const user = await User.findOne({firstName: 'John'});
console.log(user.toJson(null, 2));
# unset() - async
this.unset(keys: string | string[], options?: UpdateOneOptions)
unsets key or keys passed from the database and model
data.
/**
* Data: {
* _id: 5f43e78c9da24b1444d7c998,
* firstName: 'John',
* lastName: 'Doe',
* userId: 5e0ee7f56a5df504e669f876,
* about: {
address: 'No 10, some street avenue.',
* brothers: ['Paul', 'Peter'],
* hobbies: ['Eat', 'Code', 'Sleep']
* }
* }
*/
const user = await User.findById('5f43e78c9da24b1444d7c998');
await user.unset('userId');
await user.unset(['about.brothers', 'about.hobbies'])
console.log(user)
/**
* User {
data {
* _id: 5f43e78c9da24b1444d7c998,
* firstName: 'John',
* lastName: 'Doe',
* about: {
* address: 'No 10, some street avenue.',
* }
* }
* }
*/
# update() - async
this.update(data?: {})
is a shortcut for set and save, it will throw an error if there is no _id in the
model's data.
await user.update({
firstName: "Lorem",
lastName: "Ipsum"
})
// Same as
await user.set({
firstName: "Lorem",
lastName: "Ipsum"
}).save();
Note: Updates will not occur if there are no changes in data.
# updateRaw() - async
this.updateRaw(updateQuery: {}, options?: UpdateOneOptions)
unlike this.update()
allows you to perform other
operations other than {$set} operations.
The updateQuery has to be a mongo native query syntax.
Example: Let's increment orders of John Doe's profile
/**
* Data {
* firstName: 'John',
* lastName: 'Doe',
* orders: 10,
* }
*/
const john = await user.findOne({firstName: 'John'});
await john.updateRaw({
$inc: {orders: 1}
})
# $useSchema()
this.$useSchema(schema: {})
is used to register the model's schema, with schemas you get a validation check and auto
generated fields.
Note: $useSchema
must be in the constructor() but will be called for you if you have static schema
set.
// Import Xpress-Mongo Schema builder.
const {is} = require('xpress-mongo');
// Build Schema
const UserSchema = {
firstName: is.String().required(),
lastName: is.String().required(),
orders: is.Number(0),
updatedAt: is.Date().required(),
createdAt: is.Date().required()
}
class Users extends connection.model('users') {
constructor() {
super()
this.$useSchema(UserSchema);
}
}
module.exports = Users;
# validate()
this.validate()
is used internally to validate model's data against model schema whenever you call this.update()
, this.save()
and Model.new()
. Throws error if validation fails.
Eg: using the schema defined in $useSchema
const user = await User.findById('5f43e78c9da24b1444d7c998');
user.set('firstName', ['an array instead of string']);
console.log(user.validate())
// TypeError: (firstName) is not a String