# 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;
Copied!
# is - SchemaBuilder
xpress-mongo comes with predefined schema builders, they are stored in an exported variable named is
const {is} = require('xpress-mongo');
Copied!
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() }
Copied!
# 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"]) }
Copied!
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) }
Copied!
# 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() }
Copied!
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.` )
Copied!
# 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') }
Copied!
# 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") }
Copied!
# 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), }
Copied!
# 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 })), }
Copied!
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() }
Copied!
# 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"])
Copied!
# 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) }
Copied!
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 * } * } */
Copied!
# 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)
Copied!
# 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');
Copied!
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);
Copied!
# 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);
Copied!
# 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
Copied!
# 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)
Copied!
# 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" }) }
Copied!
# 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);
Copied!
# 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] });
Copied!
# 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!`)
Copied!
# 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", // }
Copied!
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.
Copied!
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", // }
Copied!
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() }
Copied!
# 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`); } }
Copied!
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() }
Copied!