Search…
Schema
Delightfully simple, feature packed and customisable tool for validation and sanitization of any kind of data. Can be used on the client side as well as on the server side.
Source code is hosted on GitHub
  • Works with React, React Native, Angular, Vue or NodeJs
  • Plentitude of validation methods for different data types
  • Many useful sanitization methods for data normalization
  • Typed from tail to toe for amazing developer experience
  • Very intuitive and easy to use
  • Extremely flexible and extendable
  • Supports sync and async logic
  • Encourages you to build reusable schemas
  • Combine multiple schemas into more complex ones
  • Conditionally connect schemas for complex scenarios
  • Write custom validation and sanitization logic with ease
  • Easily override error messages
  • Comes translated into 6 different languages (English, German, French, Italian, Spanish and Russian)
  • Easily add a new language or override translation
yarn
npm
yarn add @corets/schema
npm install --save @corets/schema

Concepts

There are two ways to run validations. For simple things like one-liners where you simply want to know if a value matches certain criteria, and get a true or false as result, you can use the Schema.test() method. For proper validation, with error messages instead of a simple true / false, you can use the Schema.validate() method.
Every schema specialises on a specific data type and comes with various assertion and sanitization methods. Some methods you'll be able to find on every schema, others are exclusive to a specific kind of schema.
Assertions are used for validation purposes to ensure that a value is valid.
Sanitization methods are called before validation and are used to normalise the underlying values. For example, you can ensure that a string is capitalised and all object keys are camel-cased. Sanitization can help you reduce the number of validation errors by proactively fixing them in the first place. All sanitization methods start with the prefix to, for example: string.toTrimmed(), string.toCamelCase().
Check out this React form library, it has a support for schemas and changes the way you work with forms:

Quick start

Here is an example of all the available schemas and how to import them:
import {
string,
number,
array,
boolean,
date,
object,
mixed
} from "@corets/schema"
Let's describe a simple user object.
  • Property email must be of type string and a valid email address
  • Property fullName must be a string between 3 and 100 characters
  • Property roles must be an array containing at least one role, valid roles are admin, publisher and developer, no duplicates are allowed
  • Property tags must be an array of strings, each at least 3 characters long and consisting of letters and dashes
This schema contains some validation as well as some sanitization logic.
import { array, object, string } from "@corets/schema"
const roles = ["admin", "publisher", "developer"]
const userSchema = object({
email: string().email(),
fullName: string().min(3).max(100),
roles: array().min(1).someOf(roles).toUnique(),
tags: array(string().min(3).alphaDashes())
})
Quick check if an object is valid according to the schema:
const valid = userSchema.test({ ... })
if (valid) {
// continue ...
}
Get a detailed list of errors:
const errors = userSchema.validate({ ... })
if ( ! errors) {
// continue ...
}
The errors object would look something like this:
{
"field": ["first error", "second error"],
"nested.field": ["first error", "second error"],
}
Run sanitizers without any validation:
const sanitizedValue = userSchema.sanitize({ ... })
Test, validate and sanitize at the same time:
const [valid, sanitizedValue] = userSchema.sanitizeAndTest({ ... })
const [errors, sanitizedValue] = userSchema.sanitizeAndValidate({ ... })

Combining schemas

Schemas can be freely combined with one another. When describing arrays and objects there is no way around this. Here is an example of two schemas being combined:
import { array, string } from "@corets/schema"
const usernameSchema = string().alphaNumeric().between(3, 10)
const usernameListSchema = array(usernameSchema).min(3)
const errors = usernameListSchema.validate(["foo", "bar", "baz"])
Consider writing schemas that are reusable.

Conditional validations

Schemas can be logically linked together using methods Schema.and(), Schema.or() and Schema.also(). A Schema.and() relation will only be executed if the parent schema, that it is linked to, could validate successfully. A Schema.or() relation will only execute if the parent schema failed, the alternative schema will be attempted instead. A Schema.also() relation will execute regardless of the validation status of the parent schema.
Example of a Schema.and() relation, this schema will first ensure that the value is a string, and only if the first validation passes, it will test if the value is a numeric string:
import { string } from "@corets/schema"
string().and(string().numeric())
Example of a Schema.or() relation, this schema will first check if the value is a number, and only if it's not, it will test if that value is a numeric string:
import { number, string } from "@corets/schema"
number().or(string().numeric())
Example of an Schema.also() relation, this schema will execute both parts regardless of the validation outcome of the parent schema:
import { string } from "@corets/schema"
string().also(string().numeric())
Conditional schemas can be wrapped into a function, this allows you to define schemas dynamically during the validation:
import { string } from "@corets/schema"
string().and(() => string().numeric())
string().or(() => string().numeric())
string().also(() => string().numeric())
You can freely define validations and sanitizers at runtime using logical links.
Logical links can be used to attach custom validation logic:
import { string } from "@corets/schema"
string().min(3).max(20)
.and(async (v) => await isUsernameTaken(v) && "Username is already taken")

Custom validations

Methods Schema.and(), Schema.or() and Schema.also() do not always have to return another schema. They can be used for your custom validation functions. A custom validation function can return either another schema, a validation result, or an error message.
Example of a validation function returning an error message:
import { number } from "@corets/schema"
const customValidation = (value: any) => value < 12 && "Must be bigger than 12"
number().and(customValidation)
number().or(customValidation)
number().also(customValidation)
You can also return multiple error messages at once using an array.
Example of a validation function returning a schema:
import { string } from "@corets/schema"
const customValidation = (value: any) => value.includes("@") && string().email()
string().and(customValidation)
string().or(customValidation)
string().also(customValidation)
Example of a validation function returning schema validation errors:
import { string } from "@corets/schema"
const customValidation = (value: any) =>
value.includes("@") && string().email().verify(value)
string().and(customValidation)
string().or(customValidation)
string().also(customValidation)

Custom sanitizers

You can add a custom sanitizer with the method Schema.map(), here is an example of a sanitizer that converts everything to a string:
import { string } from "@corets/schema"
const customSanitizer = (value: any) => value.toString()
const sanitizedValue = string().map(customSanitizer).sanitize(1234)

Translations

All the translation logic is handled by this library:
You can change default language:
import { schemaTranslator } from "@corets/schema"
schemaTranslator.setLanguage("en")
You can also request a specific language during validation:
import { string } from "@corets/schema"
string().validate("foo", { language: "ru", fallbackLanguage: "en" })
string().verify("foo", { language: "ru", fallbackLanguage: "en" })
string().sanitizeAndValidate("foo", { language: "ru", fallbackLanguage: "en" })
string().sanitizeAndVerify("foo", { language: "ru", fallbackLanguage: "en" })
Get a full list of all translations:
import { schemaTranslator } from "@corets/schema"
console.log(schemaTranslator.getTranslations())
You can find all locales here. For further examples of how to replace translations, add new languages, etc., please have a look at the @corets/translator docs.

Schema.test()

This method simply indicates whether a value is valid or not by returning a boolean.
Example of a failed assertion:
import { string } from "@corets/schema"
const schema = string().min(3).alphaNumeric()
// false
const valid = schema.test("foo-bar")
if (valid) {
// continue ...
}

Schema.validate()

This method returns a validation result containing detailed error messages about why a value did not pass validation.
Example of a failed validation:
import { string } from "@corets/schema"
const schema = string().min(3).alphaNumeric()
const errors = schema.validate("foo-bar")
if ( ! errors) {
// continue ...
}
This is what the validation result might look like:
{
"field": ["first error", "second error"],
"nested.field": ["first error", "second error"],
}
When validating anything but an object, you'll find all the error messages under the key self.

Schema.verify()

This method works the same as Schema.validate(), except it returns errors in a slightly different format. This format contains additional information about each error message and can be used for further processing of validation errors.
Example of a failed validation:
import { string } from "@corets/schema"
const errors = string().min(10).verify("foo")
Here is an example of how the error messages would look like:
[
{
"type": "string_min",
"message": "Must be at least \"2\" characters long",
"args": [10],
"value": "foo",
"link": undefined,
"path": undefined
}
]

Schema.sanitize()

This method applies sanitizers and returns the sanitized value:
import { string } from "@corets/schema"
const schema = string().toTrimmed().toCamelCase()
const value = schema.sanitize(" foo bar ")
All sanitization methods start with the prefix to, for example: string.toTrimmed().

Schema.sanitizeAndTest()

This method applies sanitizers on the underlying value and runs validation against the sanitized version. It returns a boolean indicating whether the value is valid, and the sanitized version of the value. Basically this calls the methods Schema.sanitize() and Schema.test() sequentially.
Example of a successful test with sanitizers:
import { string } from "@corets/schema"
const schema = string().min(4).toCamelCase()
const [valid, value] = schema.sanitizeAndTest("foo bar")
Example of a failed test with sanitizers:
import { string } from "@corets/schema"
const schema = string().min(4).toTrimmed()
const [valid, value] = schema.sanitizeAndTest(" foo ")
As you can see, even though the string " foo " has a length greater than 4. During the sanitization, value gets trimmed, becomes"foo" and therefore the test will fail.
All sanitization methods start with the prefix to, for example: string.toTrimmed().

Schema.sanitizeAndValidate()

This method applies sanitizers on the underlying value and runs validation against the sanitized version. It returns a validation result containing detailed error messages about why a value did not pass validation, and the sanitized version of the value. Basically this calls the methods Schema.sanitize() and Schema.validate() sequentially.
Example of a successful validation with sanitizers:
import { string } from "@corets/schema"
const schema = string().min(4).toCamelCase()
const [errors, value] = schema.sanitizeAndValidate("foo bar")
Example of a failed validation with sanitizers:
import { string } from "@corets/schema"
const schema = string().min(4).toTrimmed()
const [errors, value] = schema.sanitizeAndValidate(" foo ")
This is what the errors would look like:
{
"self": ["Must be at least \"4\" characters long"],
}

Schema.sanitizeAndVerify()

This method works exactly the same as Schema.anitizeAndValidate() except that it returns error messages in a different format with additional information that can be used for further processing. Take a look at Schema.verify() to learn more about its use cases.
All sanitization methods start with the prefix to, for example: string.toTrimmed().

Schema.testAsync()

This is an async version of the Schema.test() method, that allows you to use async validators and sanitizers.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.validateAsync()

This is an async version of the Schema.validate() method, that allows you to use async validators.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.verifyAsync()

This is an async version of the Schema.verify() method, that allows you to use async validators.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.sanitizeAsync()

This is an async version of the Schema.sanitize() method, that allows you to use async sanitizers.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.sanitizeAndTestAsync()

This is an async version of the Schema.sanitizeAndTest() method, that allows you to use async validators and sanitizers.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.sanitizeAndValidateAsync()

This is an async version of the Schema.sanitizeAndValidate() method, that allows you to use async validators and sanitizers.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.sanitizeAndVerifyAsync()

This is an async version of the Schema.sanitizeAndVerify() method, that allows you to use async validators and sanitizers.
This library has no async validation or sanitization methods itself, but you can add your own.

Schema.also()

Connects multiple schemas or attaches a custom validation function to a schema. Connections made with this link are invoked regardless of the status of the parent schema. This is perfect when you try to combine multiple schemas or attach a custom validation function to a schema to make it behave like one single unit.
import { string } from "@corets/schema"
const usernameSchema = string().alphaNumeric()
const advancedUsernameSchema = string().min(3).max(20).also(usernameSchema)
const uniqueUsernameSchema = advancedUsernameSchema.also(
async (v) => await isUsernameTaken(v) && "Username already taken"
)
See also custom validations for more examples.

Schema.and()

Logically connects multiple schemas, or a custom validation function to a schema. Schemas and validation functions connected using this link will only be executed if the parent schema validated successfully. This is a good way to connect expensive validation logic, like a database call, and make sure that it is only invoked when it's really necessary.
import { string } from "@corets/schema"
const usernameSchema = string().alphaNumeric()
const advancedUsernameSchema = string().min(3).max(20).and(usernameSchema)
const uniqueUsernameSchema = advancedUsernameSchema.and(
async (v) => await isUsernameTaken(v) && "Username already taken"
)
See also custom validations and conditional validations for more examples.

Schema.or()

Logically connects multiple schemas, or a custom validation function to a schema. Schemas and validation functions connected using this link will only be executed if the parent schema validation has failed. This is useful if you want to provide an alternative validation procedure.
import { string } from "@corets/schema"
const usernameOrEmail = string()
.alphaNumeric().min(3).max(20)
.or(v => v.includes("@") && string().email())
See also custom validations and conditional validations for more examples.

Schema.map()

Attach a custom sanitizer function to a schema.
import { mixed } from "@corets/schema"
mixed().map(v => v.toString())
See also custom sanitizers for more examples.

schema()

This factory is used as a central entry point to create any kind of schema by providing the default value first.
import { schema } from "@corets/schema"
const stringSchema = schema("foo").string()
The example above is equivalent to this:
import { string } from "@corets/schema"
const schema = string().toDefault("foo")
Defining a default value is optional, but might be useful when validating forms.

schema.string()

Create a new string() schema starting with the default value:
import { schema } from "@corets/schema"
schema("foo").string()

schema.number()

Create a new number() schema starting with the default value:
import { schema } from "@corets/schema"
schema(123).number()

schema.boolean()

Create a new boolean() schema starting with the default value:
import { schema } from "@corets/schema"
schema(true).boolean()

schema.date()

Create a new date() schema starting with the default value:
import { schema } from "@corets/schema"
schema(new Date()).date()

schema.array()

Create a new array() schema starting with the default value:
import { schema } from "@corets/schema"
schema(["foo"]).array()

schema.object()

Create a new object() schema starting with the default value:
import { schema } from "@corets/schema"
schema({ foo: "bar" }).object()

schema.mixed()

Create a new mixed() schema starting with the default value:
import { schema } from "@corets/schema"
schema("foo").mixed()

string()

Contains various validators and sanitisers for strings:
import { string } from "@corets/schema"
string()
Create a schema instance without the factory function:
import { StringSchema } from "@corets/schema"
new StringSchema()

string.required()

Value must be a non-empty string:
import { string } from "@corets/schema"
string().required()
string().required(true, "Message")
string().required(() => true, () => "Message")

string.optional()

Value might be a string, opposite of string.required():
import { string } from "@corets/schema"
string().optional()
string().optional("Message")
string().optional(() => "Message")

string.equals()

String must be equal to the given value:
import { string } from "@corets/schema"
string().equals("foo")
string().equals("foo", "Message")
string().equals(() => "foo", () => "Message")

string.length()

String must have an exact length:
import { string } from "@corets/schema"
string().length(3)
string().length(3, "Message")
string().length(() => 3, () => "Message")

string.min()

String must not be shorter than given value:
import { string } from "@corets/schema"
string().min(3)
string().min(3, "Message")
string().min(() => 3, () => "Message")

string.max()

String must not be longer than given value:
import { string } from "@corets/schema"
string().max(3)
string().max(3, "Message")
string().max(() => 3, () => "Message")

string.between()

String must have a length between min and max:
import { string } from "@corets/schema"
string().between(3, 6)
string().between(3, 6, "Message")
string().between(() => 3, () => 6, () => "Message")

string.matches()

String must match given RegExp:
import { string } from "@corets/schema"
string().matches(/^red/)
string().matches(/^red/, "Message")
string().matches(() => /^red/, () => "Message")

string.email()

String must be a valid email address:
import { string } from "@corets/schema"
string().email()
string().email("Message")
string().email(() => "Message")

string.url()

String must be a valid URL:
import { string } from "@corets/schema"
string().url()
string().url("Message")
string().url(() => "Message")

string.startsWith()

String must start with a given value:
import { string } from "@corets/schema"
string().startsWith("foo")
string().startsWith("foo", "Message")
string().startsWith(() => "foo", () => "Message")

string.endsWith()

String must end with a given value:
import { string } from "@corets/schema"
string().endsWith("foo")
string().endsWith("foo", "Message")
string().endsWith(() => "foo", () => "Message")

string.includes()

String must include a given substring:
import { string } from "@corets/schema"
string().includes("foo")
string().includes("foo", "Message")
string().includes(() => "foo", () => "Message")

string.omits()

String must not include a given substring:
import { string } from "@corets/schema"
string().omits("foo")
string().omits("foo", "Message")
string().omits(() => "foo", () => "Message")

string.oneOf()

String must be one of the whitelisted values:
import { string } from "@corets/schema"
string().oneOf(["foo", "bar"])
string().oneOf(["foo", "bar"], "Message")
string().oneOf(() => ["foo", "bar"], () => "Message")

string.noneOf()

String must not be one of the blacklisted values:
import { string } from "@corets/schema"
string().noneOf(["foo", "bar"])
string().noneOf(["foo", "bar"], "Message")
string().noneOf(() => ["foo", "bar"], () => "Message")

string.numeric()

String must contain numbers only, including floats:
import { string } from "@corets/schema"
string().numeric()
string().numeric("Message")
string().numeric(() => "Message")

string.alpha()

String must contain letters only:
import { string } from "@corets/schema"
string().alpha()
string().alpha("Message")
string().alpha(() => "Message")

string.alphaNumeric()

String must contain numbers and letters only:
import { string } from "@corets/schema"
string().alphaNumeric()
string().alphaNumeric("Message")
string().alphaNumeric(() => "Message")

string.alphaDashes()

String must contain letters and dashes - only:
import { string } from "@corets/schema"
string().alphaDashes()
string().alphaDashes("Message")
string().alphaDashes(() => "Message")

string.alphaUnderscores()

String must contain letters and underscores _ only:
import { string } from "@corets/schema"
string().alphaUnderscores()
string().alphaUnderscores("Message")
string().alphaUnderscores(() => "Message")

string.alphaNumericDashes()

String must contain letters, numbers and dashes only:
import { string } from "@corets/schema"
string().alphaNumericDashes()
string().alphaNumericDashes("Message")
string().alphaNumericDashes(() => "Message")

string.alphaNumericUnderscores()

String must contain letters, numbers and underscores only:
import { string } from "@corets/schema"
string().alphaNumericUnderscores()
string().alphaNumericUnderscores("Message")
string().alphaNumericUnderscores(() => "Message")

string.date()

String must be a valid ISO date string:
import { string } from "@corets/schema"
string().date()
string().date("Message")
string().date(() => "Message")