# xpress-mongo Schema
Mongodb is a schemaless database, so you have the choice to use without a schema. The schema xpress-mongo provides does not force any type unless defined. it serves a structure for your models and gives you an insight of what your data should look like in the database.
# Basic Example
const {is} = require('xpress-mongo');
class Orders extends connection.model('orders') {
// Set Model Schema
static schema = {
itemId: is.ObjectId().required(),
itemTitle: is.String(),
qunatity: is.Number().required(),
status: is.String('pending').required(),
paypalPaymentId: is.String(),
updatedAt: is.Date().required(),
createdAt: is.Date().required()
}
}
module.exports = Orders;
# is - SchemaBuilder
xpress-mongo comes with predefined schema builders, they are stored in an exported variable named is
const {is} = require('xpress-mongo');
Note: all schema functions returns an instance of XMongoDataType
# is.Any()
Set a field to accept any type of data passed in.
const UserSchema = {
field: is.Any().required()
}
# is.Array()
Set a field to type of Array
. Has default value of () => []
.
const UserSchema = {
hobbies: is.Array(),
// with default value
languages: is.Array(() => ["en"])
}
Note: when declaring a default value for is.Array
and is.Object
you must use a function. This is to prevent any
mutation on default values.
# is.Boolean()
Set a field to type of Boolean
. Has default value of false
.
const UserSchema = {
isAdmin: is.Boolean(),
// with default value
sendNewsletters: is.Boolean(true)
}
# is.CustomValidator()
Set a custom validator and error on the fly using the custom validator schema type.
// Syntax
is.CustomValidator(validatorFunction, errorMessage);
// Example
const usernameValidator = is.CustomValidator((username) => {
return new RegExp(/* Some Check */).test(username)
},
'Username contains invalid characters.'
)
// Usage
const UserSchema = {
username: usernameValidator.required()
}
The is.CustomValidator
returns a type of XMongoDataType
, meaning you have same instanced methods like every other
schema type.
Note: The error argument can also accept a function.
const usernameValidator = is.CustomValidator((username) => {
return new RegExp(/* Some Check */).test(username)
},
(key) => `{$key} contains invalid characters.`
)
# is.Date()
Set a field to type of Date
. Has default value of the current date: `new Date()
const PostSchema = {
createdAt: is.Date(),
// with default value
publishedAt: is.Date('Fri, 03 Apr 2020 00:00:00 GMT')
}
# is.InArray()
Checks if the value of a field is in a specified array.
const allowedStatus = ["pending", "completed", "cancelled"];
const genders = ["unknown", "male", "female"];
const UserSchema = {
gender: is.InArray(genders),
// With default value 'pending'
status: is.InArray(allowedStatus, "pending")
}
# is.Number()
Set a field to type of Number
. Has default value of 0
.
const VideoSchema = {
views: is.Number(),
// with default value
minimumAge: is.Number(16),
}
# is.Object()
Set a field to type of Object
. Has default value of () => {}
.
const UserSchema = {
deviceId: is.Object(),
// with default value
address: is.Object(() => ({
city: null,
state: null,
country: null
})),
}
Note: when declaring a default value for is.Array
and is.Object
you must use a function. This is to prevent any
mutation on default values.
# is.ObjectId()
Set a field to type of mongodb ObjectId
and has no default value.
const PostSchema = {
userId: is.ObjectId().required()
}
# is.String()
Set a field to type of String
. Has no default value.
Note: If an array
is passed instead of a string
is.InArray() is used.
const UserSchema = {
email: is.String(),
// with default value
status: is.String('pending'),
// Array as option means strictly any of these
role: is.String(["admin", "subscriber", "editor"])
}
// Using array as option is same as
is.InArray(["admin", "subscriber", "editor"])
# is.Types()
Set a field to multiple Types
. Inherits default value of the first type if any.
const CommentSchema = {
// String or ObjectId (Default: 'admin')
author: is.Types([
is.String('admin'),
is.ObjectId()
]),
// Number or ObjectId (Default: 1)
published: is.Types([
is.Boolean(true),
is.Number()
]).default(1)
}
The properties of the first type in the array will be inherited by is.Types()
. The default string defined on line
4 admin
will be inherited.
Default variables can also be defined using .default(value)
as seen in line 12 above.
# is.Uuid()
Set a field to type of "Uuid String" (opens new window) specifying the
version of uuid as first argument.
The versions of uuid are 1, 3, 4 & 5
, if you don't have any idea of the version togo with then use 4.
Why
4? Because 4 requires no extra configurations
class Transaction extends model('transactions') {
// Set Model Schema
static schema = {
id: is.Uuid(4, {/*..config..*/}).required(),
amount: is.Number().required()
}
}
console.log(Transaction.make({amount: 200}));
/**
* Transaction {
* data: {
* id: 'efae452f-bd6f-4349-8cbf-7a755ef88702',
* amount: 200
* }
* }
*/
# Schema Instance Methods
Methods available on XMongoDataType
instance are:
# cast()
# Args: (cast: Function)
Sets/Overrides the cast function of the schema. The cast function receives two arguments when it's been called. Whatever is returned by the cast function is sent to the database.
# value
- Value of the current field. key
- Key of the current field.
const stringToDate = (value, key) => {
return new Date(value);
}
new XMongoDataType('ValidDateString', true).cast(stringToDate)
# default()
# Args: ($default: any | function)
Set/Override the default value of a schema. When default values are defined, xpress-mongo uses them and won't throw any error when a field is required but undefined.
Note: if a function
is passed as a default value, it will be executed, and the return value will be used.
is.String('pending');
// is same with
is.String().default('pending');
// is same with
is.String().default(() => 'pending');
usage depends on your preference, for readability you can decide to use .default()
.
Can also be used to override any previous default values.
// Default value overwriten to '404'
is.Number(200).default(404);
# optional()
Sets required to false
. When a field is optional xpress-mongo does not throw any error when it's undefined
but
will validate when a value is defined.
is.String().optional()
// is same with
is.String().required(false);
# undefined()
Sets default value to undefined
. if value of field is undefined and required xpress-mongo will throw an **
error**.
is.Number() // default value = 0
is.Number().undefined() // default value = undefined
is.Number().default(undefined) // default value = undefined
# required()
# Args: (value?: boolean)
The required method sets if a particular field required or not.
is.String().required()
// set to false
is.String().required(false)
# requiredIf()
# Args: (fn: RequiredIf)
Sets field to required depending on the boolean value returned by the function passed.
Note: Function passed gets model instance as first argument.
const FileSchema = {
// Accept either "image" | "audio"
type: is.String(["image", "audio"]).required(),
// Require duration only if file type is "audio"
duration: is.String().requiredIf(file => {
// check if current instance type is "audio"
return file.data.type === "audio"
})
}
# validator()
Sets/Overrides the validator function/functions of the schema.
# Single Validator Function
The validation function receives the current value of the field being validated and can return either: true|false
new XMongoDataType('adultOnly').validator(age => age > 18);
# Multiple Validators (and|or)
xpress-mongo also provides a method for you to validate against multiple functions using the and
or or
object rules
and
- All functions must return true
or
- Any of the functions must return true
Only one of the keys can be defined. if both is defined or
will be used.
new XMongoDataType('name').validator({
or: [function1, function2]
});
new XMongoDataType('name').validator({
and: [function1, function2]
});
# validatorError()
# Args: (errorFn: Function)
Set/Override schema validation error. The function received given passed the current name of the field being validated as first argument.
new XMongoDataType('name').validatorError(() => "Error Message")
// or with key
new XMongoDataType('name').validatorError(key => `${Key} is not valid!`)
# Strict Schema
# Version >=1.1.0
- Property: static strict
- Type: boolean | {removeNonSchemaFields: boolean}
By default, xpress-mongo allows fields not defined in schema to be added to your database if they exist. For example:
class User extends XMongoModel {
static schema = {
firstName: is.String(),
lastName: is.String(),
}
}
// Add new document
const user = await User.new({
email: "hello@example.com",
firstName: "John",
lastName: "Doe",
});
console.log(user.data)
// {
// email: "hello@example.com",
// firstName: "John",
// lastName: "Doe",
// }
email
will be added to the database even when it does not exist in the schema. To prevent this, a static strict
property must be declared in the model like so:
class User extends XMongoModel {
// Enable Strict
static strict = true;
static schema = {
firstName: is.String(),
lastName: is.String(),
}
}
// Add new document
const user = await User.new({
email: "hello@example.com",
firstName: "John",
lastName: "Doe",
});
// Error: STRICT: "email" is not defined in schema.
If strict
is enabled, xpress-mongo will throw an error anytime a field not defined in the schema is being validated,
saved or updated.
# Remove Non Schema Fields
Instead of throwing an error when a field not defined in schema is found, we can tell xpress-mongo to remove the unknown
fields for us by setting strict to { removeNonSchemaFields: true }
class User extends XMongoModel {
// Enable strict
static strict = {removeNonSchemaFields: true};
static schema = {
firstName: is.String(),
lastName: is.String(),
};
}
// Add new document
const user = await User.new({
email: "hello@example.com",
firstName: "John",
lastName: "Doe",
});
console.log(user.data)
// {
// firstName: "John",
// lastName: "Doe",
// }
email
will be ignored and removed from data because it is not defined in schema.
# Custom Schema Type
Custom schema types can be created by making a function that returns new instance of XMongoDataType
class and
providing the following:
- Name of schema.
- Validator function.
- Validator error message.
- Cast function (optional).
- Default value (optional).
The default types above were all created same way and bundled with the package.
function customSchema(defaultValue) {
return new XMongoDataType('SchemaName', defaultValue).validator(currentFieldValue => {
// do something with currentFieldValue
return true | false;
}).validatorError(currentField => `${currentField} is not what we want!`)
}
const modelSchema = {
field: customSchema('A default value.').required()
}
# Examples
Below are examples of how is.String()
&& is.Array()
was created.
const is = {
String(def = undefined) {
return new XMongoDataType('String', def).validator(str => typeof str === "string").
validatorError((key) => `(${key}) is not a String`);
},
Array(def = () => []) {
return new XMongoDataType('Array', def).validator(array => Array.isArray(array)).
validatorError((key) => `(${key}) is not an Array`);
}
}
The method of using a function that returns a new XMongoDataType
is only a concept to provide re-usability. There may
be cases where you don't need to reuse i.e The Schema only applies to one Model's field, you can create them like below:
isAnAdult
- checks if the age passed is old enough
isSixNumbers
- checks if the ticket number is a
valid ticket number.
const isAnAdult = new XMongoDataType('isAnAdult').validator(age => age >= 18).
validatorError(() => `Too young to see this movie.`)
const isValidTicket = new XMongoDataType('isValidTicket').validator(str => str && !isNaN(str) && str.length === 6)
// with key - Name of current field being validated
.validatorError((key) => `${key} is not a six digits number.`)
const AdultMovieTicketSchema = {
name: is.String().required(),
age: isAnAdult.required(),
ticketNumber: isValidTicket.required()
}