Schema Validation With Joi

Schema Validation With Joi

Motivation

Validation is much like testing. Many programmers avoid it because it adds development time and is not strictly necessary for project completion; however, its long term benefits are indisputable. The number one use case for schema validation in Node is persisting data. Many Node applications rely on a document-oriented, dynamic-schema databases such MongoDB. Having a dynamic schema, as opposed to the traditional relation database with a strict schema, opens up a can of worms. There is nothing that is forcing documents in this database to follow the same format, this can be powerful and dangerous.

For example, lets assume we have database for storing blog posts, a post may look like this

{
  title: 'Schema Validation With Joi'
  author: 'Zack Liston',
  content: ''
}

If we decide we want to add a subtitle to some posts we can simply update the posts we want. There's no need for running a migration, or adding columns to tables, because we have neither columns or tables. We can update our blog post to have a subtitle, while ignoring others.

{
  title: 'Schema Validation With Joi',
  subtitle: 'A crash course',
  author: 'Zack Liston',
  content: ''
}
{
  title: 'An intro to testing in nod',
  author: 'Zack Liston',
  content: ''
}

On the flipside, there's nothing protecting us from potential mistakes. We could insert a document that has a misspelled key.

{
  qitle: 'Schema Validation With Joi'
  author: 'Zack Liston',
  content: ''
}

Or an unexpected data type.

{
  title: {
    bad: 'data'
  },
  author: 'Zack Liston',
  content: ''
}

Or a document that has nothing in common with the others.

{
  _id: 1,
  name: 'Zack Liston'
  githubUsername: 'zackliston'
}

You may think that you're a careful enough programmer to never make these mistakes, and you may well be, but sometimes it is out of your control. The information for the document may come from an outside source, such as an externally facing API. You can never trust your input.

Joi

The first solution you may think of is writing a series of checks yourself using if-statements and the typeof operator. This is a tedious, error-prone method. Luckily for us, this is a widespread problem, and an elegant solution has been created. That solution is Joi.

Joi is a tool with which we can create blueprints for our data. It has an extensive API and provides validation tools for nearly any kind of data you can imagine. You can even set defaults, conditionals, and units. You can automatically transform data (eg stringified numbers to numbers) and apply powerful string validation methods. Joi is the go-to tool for schema validation

Example

Let's start with a user document. At a very basic level we need two keys, which are both required. Let's make sure the id is a guid and the username is an alphanumeric string that is at least 8 characters

var schema = joi.object().keys({
  id: joi.string().guid().required(),
  username: joi.string().alphanum().min(8).required()
});

Next let's add a timeCreated key that contains a date in the javascript millisecond format that defaults to now

var schema = joi.object().keys({
  id: joi.string().guid().required(),
  username: joi.string().alphanum().min(8).required(),
  timeCreated: joi.date().timestamp('javascript').default(Date.now)
});

Ok. Let's add a user type, but we only want to allow certain values.

var schema = joi.object().keys({
  id: joi.string().guid().required(),
  username: joi.string().alphanum().min(8).required(),
  timeCreated: joi.date().timestamp('javascript').default(Date.now),
  type: joi.string().valid(['member', 'admin']).required()
});

Finally, let's add a contact object that contains several ways to contact this user, all of which are optional.

var schema = joi.object().keys({
  id: joi.string().guid().required(),
  username: joi.string().alphanum().min(8).required(),
  timeCreated: joi.date().timestamp('javascript').default(Date.now),
  type: joi.string().valid(['member', 'admin']).required(),
  contact: joi.object().keys({
    email: joi.string().email,
    phone: joi.string().regex(/^(\+0?1\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/)
  })
});

Now that we have our schema we can validate any json document against it. If the document fails validation, a 422 is an appropriate HTTP status code to return.

var schema = ...;
var document = {...};

Joi.validate(document, schema, function (err, value) {
  // Value is the parsed and validated document.
  if (err) {
    // Gracefully handle any errors.
  }
});

TL;DR

We cannot trust input, especially when it's data that will be persisted. Joi is a excellent tool to validate and parse JSON data.

Share This