Nano schema helps you validate that JavaScript objects match a schema. The goal of the project was to create a lightweight, high performance and extensible library with no dependencies. The current size of this library is: 1.25KB
npm install nano-schema
or if (like me) you use Yarn:
yarn add nano-schema
Let's start with an example:
const schema = new Schema({
name: String,
age: Number,
familyMembers: [String],
address: {
city: String,
street: String,
},
});
Let's break it down. The schema we created above will put the next constraints in place:
object.name
- Can only be a Stringobject.age
- Can only be a Numberobject.familyMembers
- Can only be an Array, and its list items can only be of type String.object.address
- Can only be an Object, and its only allowed props arecity
andstreet
and both on them must be of type String. In this example, there are no other constraints on the schema, non of the fields is required or has any additional restrictions.
Its very simple, in order to verify that an object passes all the constraints we need to use the validate
method:
schema.validate({
name: "Samuel",
age: "23",
familyMembers: ["brother name", "sister name", "etc"],
address: {
city: "Tel-Aviv",
address: "Hertzel!!!",
},
});
If the passed object adheres to the schema, then the method will not throw an error.
When an object fails to validate, the validate
method will throw an error with a message explaining exactly how the validation failed, and what property caused the error.
Let's show an example with the schema above:
schema.validate({
/* ... */
address: {
city: 42,
},
/* ... */
});
In this object, everything is valid except object.address.city
, so the validate
method will throw:
The property "address.city" is not of the correct type
Up until now we have seen how to set up a field with only the "Type" constrain, But what happens when we want to add additional constrains? Well, it's very simple, instead of using the "short" syntax we used before, we are going to use the "verbose" one. But don't worry! it's still easy!
So, let's say we want to make a field be required, here is how we do that:
const schema = new Schema({
name: {
type: String,
required: true,
},
});
As you can see, it's pretty straightforward.
Instead of using the "short" field syntax, we use the "verbose" one, which means
that instead of writing name: String
, we provide an object that has a type
property, and any additional options
that the type we want to use supports, in this example we added required
.
There are two ways of specifying a "Field schema". One is short and easier to read, and the second one is "verbose" and is used when we want additional constrains on that field. like required
, enum
etc.
Here is an example of how we would use the "short" syntax to create a field that should be of type String
:
const schema = new Schema({
name: String,
});
As you can see it's pretty easy to read, and self explanatory.
The verbose version of the same "Field schema" would be:
const schema = new Schema({
name: {
type: String,
},
});
The validation will work exactly the same for both syntaxes. In fact, behind the scenes, the short version will be converted into the verbose one. So, it's practically the same.
With the Object
and Array
types the short and verbose version will look like this:
Array
// Short
const schema = new Schema({
favoriteMovies: [String],
});
// Verbose
const schema = new Schema({
favoriteMovies: {
type: Array,
child: [
{
type: String,
},
],
},
});
Object
// Short
const schema = new Schema({
address: {
city: String,
street: String,
},
});
// Verbose
const schema = new Schema({
address: {
type: Object,
child: {
city: {
type: String,
},
street: {
type: String,
},
},
},
});
As you can see in the later examples, the difference is significant. But the verbose syntax is needed because sometimes we need to add constrains to the fields.
The built in types are: String
, Number
, Boolean
, Date
, Array
and Object
.
Each type has additional options, this is not a final list, and more will be added in the near future.
String
required
- Boolean. If set to true, the field cannot be left empty, or else an error will be thrown on validation. An empty string is considered empty, and will throw.enum
- Array. Should be an array of strings that are allowed. Any other string will throw an error at validation. All the enum options must of the correct type or an error will be thrown on schema creation.test
- RegExp. When specified, the string must match the regular expression in order to pass validation.
Number
required
- Boolean. If set to true, the field cannot be left empty, or else an error will be thrown on validation.enum
- Array. Should be an array of strings that are allowed. Any other string will throw an error at validation. All the enum options must of the correct type or an error will be thrown on schema creation.coerce
- Should the validator try to coerce the value into a number? This could be very helpful when trying to validate the values of a form.
Boolean
.
Required
- Boolean. If set to true, the field cannot be left empty, or else an error will be thrown on validation.
Date
A valid date is any date that JavaScript can parse and result in a valid date object.
Required
- Boolean. If set to true, the field cannot be left empty, or else an error will be thrown on validation.
Array
Required
- Boolean. If set to true, the field cannot be left empty, or else an error will be thrown on validation.child
- Array. An array of schema fields that can be included in the array. So if a value matches any of them it is allowed. But if the schema doesn't match any of the provided field schemas then the validator will throw with an error specifying exactly which index threw.
Object
Required
- Boolean. If set to true, the field cannot be left empty, or else an error will be thrown on validation. Please note that currently an empty object will pass validation.child
- A sub schema object (Like the one passed to thenew Schema()
constructor).
A type is just an object with some methods that the schema class will invoke when needed. Here is a simplified example of how it would look (explanation below).
const customType = {
allowedProps: ["required", "enum", "etc"],
validateType(value, schema) {
/* ... */
},
validateSchema(schema) {
/* ... */
},
};
Yep, it's that simple. Each property has a specific role. Here is an explanation of what each of them does.
allowedProps
- An array that lists all the allowed properties in the type schema, excluding 'type' and 'meta' which are reserved, and added automatically.validateType
- This method is the one that should contain the logic to allow or reject a value. It should return a Boolean,true
if the value matches andfalse
otherwise. The method will receive the value itself, and the schema options for the field.validateSchema
- This method is invoked when a schema class is instantiated, and it is used to check that the schema was written correctly and doesn't contain any invalid props or typos.
Please note that onlyvalidateType
and allowedProps
props are required, your type does not need to support required
or enum
, and shouldn't have a validateSchema
method unless it adds options other that enum
and required
, since tests for those are already built in.
If you would like to see some good examples, just look inside the folder src/types
.
Adding the custom type is very easy, just add it to the validators
map in the static class. The key
should be the identifier that the user will put in the type
prop in the schema.
If you are not familiar with Map
s in JavaScript, it's pretty easy to understand, here is the MDN documentation for it.
Here is an example of adding a custom type:
// This should be the path to the custom type object we created.
const customTypeObject = require("./customType.js");
const Schema = require("schema-validator");
Schema.validators.set("customType", customTypeObject);
// Now the user can use the type by typing:
const schema = new Schema({
username: {
type: "customType",
},
});
// Or even the short way
const schema = new Schema({
username: "customType",
});
When the validation fails, a Map will be returned, in which the key
will be the name of the property that had an error, and the value
will be the error message.
An example:
const schema = new Schema({
name: {
type: String,
required: true,
},
age: Number,
});
schema.validate({
age: ["This is not a number"],
});
The code above will throw a map:
Map(2) {
"name" => "The field is required",
"age" => "The field is not of the correct type"
}
Sometimes it is necessary to add metadata to the schema, for example, to use the schema declaration in other parts of our codebase, like passing it to a helper that creates forms.
There is a global type option called "meta", it is valid on all types, even custom ones, and is reserved just for that.
Code example:
const schema = new Schema({
name: {
type: String,
required: true,
meta: {
placeholder: "Please enter your full name...",
className: "form-control",
},
},
});