Enrich your models

Adding validation to your models

Validation allows you to keep control over your data's quality and integrity.

If your existing app already has validation conditions, you may - or may not - want to reproduce the same validation conditions in your admin backend's models.

If so, you'll have to do it manually, using the below examples.

Depending on your database type, your models will have been generated in Sequelize (for SQL databases) or Mongoose (for Mongo databases).

SQL
Mongodb

In Sequelize, you add validation using the validate property:

/models/customers.js
module.exports = (sequelize, DataTypes) => {
var Customer = sequelize.define('customers', {
...
'email': {
type: DataTypes.STRING,
validate: {
isEmail: true,
len: [10,25]
}
},
...
},
...
return Customer;
};

The 2 validators above will have the following effect on your email field:

Invalid email
Email too short (not within 10-25 range)

For an exhaustive list of available validators, check out the Sequelize documentation.

In Mongoose, you add validators alongside the type property:

...
const schema = mongoose.Schema({
'createdAt': Date,
'email': {
'type': String,
'minlength': 10,
'maxlength': 25
},
'firstname': String,
...
}

This is the effect on your field:

Email is too short (not within 10-25 range)

Mongoose has no build-in validators to check whether a string is an email. Should you want to validate that a content is an email, you have several solutions:

/models/customers.js
...
const schema = mongoose.Schema({
'createdAt': Date,
'email': {
'type': String,
'match': [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Invalid email']
},
'firstname': String,
...
}

A better yet solution would be to rely on an external library called validator.js which provides many build-in validators:

/models/customers.js
import { isEmail } from 'validator';
...
const schema = mongoose.Schema({
'createdAt': Date,
'email': {
'type': String,
'validate': [isEmail, 'Invalid email']
},
'firstname': String,
...
}

You then that any invalid email is refused:

For further details on validators in Mongoose, check out the Mongoose documentation.

Adding a default value to your models

You can choose to add a default value for some fields in your models. As a result, the corresponding fields will be prefilled with their default value in the creation form:

SQL
Mongodb
/models/customers.js
module.exports = (sequelize, DataTypes) => {
var Customer = sequelize.define('customers', {
...
'firstname': {
'type': DataTypes.STRING,
'defaultValue': 'Marc'
},
...
},
...
return Customer;
};
/models/customers.js
...
const schema = mongoose.Schema({
'createdAt': Date,
'email': {
'type': String,
'default': 'Marc'
},
'firstname': String,
...
}

Adding a hook

Hooks are a powerful mechanism which allow you to automatically trigger an event at specific moments in your records lifecycle.

In our case, let's pretend we want to update a update_count field every time a record is updated:

SQL
Mongodb

To add a beforeSave hook in Sequelize, use the following syntax:

module.exports = (sequelize, DataTypes) => {
var Order = sequelize.define('orders', {
...
'update_count': {
'type': DataTypes.INTEGER,
'defaultValue': 0
},
...
},
...
Order.beforeSave((order, options) => {
order.update_count += 1;
}
);
return Order;
};

Every time the order is updated, the updateCount field will be incremented by 1:

The exhaustive list of available hooks in Sequelize are available here.

To add a hook in Mongoose on save event, you may use the following snippet:

/models/customers.js
schema.pre('save', async function() {
var newCount = this.update_count+1;
const incrementCount = () => {
this.set('update_count', newCount);
};
await incrementCount();
});

As mentioned in their documentation

Pre and post save() hooks are not executed on update(), findOneAndUpdate(), etc.

This would only work if you specifically call save in your update method.

Adding nested documents (level 2 and 3) in Mongoose

Lumber only detects Level 1 document fields in your collections. However Forest Admin is able to display Level 2 and Level 3 sub-documents if properly declared in your model.

The following model...

const mongoose = require('mongoose');
const schema = mongoose.Schema({
// Level 1
'age': Number,
'id': Number,
'name': String,
// Level 2
'address':{
'addressDetail': String,
'area': String,
'city': String,
'pincode': Number,
},
// Level 3
'contactDetails':{
'phone':{
'homePhone': String,
'mobilePhone': String,
},
'email': String,
},
// Related data
'booksRead':[{
'name': String,
'authorName': String,
'publishedBy': String,
}],
}, {
timestamps: false,
});
module.exports = mongoose.model('testCollection', schema, 'testCollection');

...will result in the following interface:

Removing a model

By default all tables/collections in your database are analyzed by Lumber to generate your models. If you want to exclude some of them to prevent them from appearing in your Forest, check out this how-to.