Our first open source library: A javascript validation library

Alright, before you say it: Yes, we know there are a few validation libraries out there but we didn’t really like any of them!

Hi,

I’m David one of the developers at Muso! A few days ago I was looking at validation libraries for our API. I found a couple of options including JOI, Parsley and ValidateJS which looked fine but weren’t exactly what I was looking for. They were either way too big and complex, didn’t have enough features or I just didn’t like their syntax.

So I decided, why not roll out my own lib. I don’t depend on other peoples motivation to continue their package and it’s a good practice. Plus I get to write a fancy medium article to advertise our lib :)

TLDR;

This validation library has easy syntax, is flexible and overall pretty awesome. If you wanna check it out and use it head over to:

We’d love your feedback!!

The idea

My requirements for the this project were quite simple:

  • easy but powerful API
  • customisable error formats

Let’s start talking about the API

The API

I see a lot of validation libraries using classes for their rules which in my opinion is way too cluttered and takes unnecessary space. Here’s an example:

{ id: [new Required(), new Type('int'), new Max(999)] }

That just looks utter shit. Yes it’s classes and instances and is probably the “right way” to do it but it’s cluttered, takes space and gets unreadable.

We talked amongst our (granted, very small) team and also to a few other developers to see what kind of syntax they might like. Our initial thought was this:

{ id: 'required, type:int, max:999' }

This is already a lot more concise and easier to read but a string is not really ideal especially if you want to inject a global config like maybe a max length for a name it would get a bit messy:

{ firstName: `required, type:int, min:${firstNameMinLength}, max:${ firstNameMaxLength` }

So the obvious choice here is to use one object for the rules:

{ 
firstName: {
required: true,
type: int,
min: firstNameMinLength,
max: firstNameMaxLength
}
}

Nested fields

Validating Objects

What do you do if you want to validate nested fields? This might be a quite common use case, for example getting back user data with their home address nested into one object like this:

{
firstName: 'John',
lastName: 'Doe',
address: {
street: 'A street',
zipcode: 'W1',
city: 'London',
country: 'UK'
}
}

You don’t just want to test that the address is an object but you probably want to make sure that the street is a string, the zip code is alphanumerical and the country code has only 2 chars.

In our validation lib you’d achieve this with a rule set that looks something like this:

{
firstName: { required: true, type: 'string' },
address: { required: true, type: 'object' }, // optional
'address.street': { required: true, type: 'string' },
'address.zipcode': { required: true, type: 'alphanum' },
'address.country': { required: true, min: 2, max: 2 }
}

This to us seems like a reasonable syntax as it’s quite clear what’s meant.

Validate arrays

One use case we had was that when the user creates an event they can select multiple dates for the event (like a repetition). For that we wanted to make sure that all dates that are being passed in are numbers. The data structure looks similar to this:

{ dates: [20170101, 20170503, 20170909] }

We wanted to make sure all our dates are integers and have the right length so our rule looks like this:

{
dates: { required: true, type: 'array', min: 1 },
'dates.*': { type: 'int', min: 20100101, max: 21001212 }
}

Again, that should be quite easy to understand:

  • Dates should be an array and have at least 1 item
  • Every item in dates should be an integer and between the given dates

Validate objects in arrays

The last use case is having nested objects in arrays, for example:

{
links: [
{ url: 'www.google.com', order: 1 },
{ url: 'www.facebook.com', order: 2 },
{ url: 'www.instagram.com', order: 3 }
]
}

You may want to make sure that every url key in the array is a string and the order is an integer. The rule set would look like this:

{
links: { type: 'array' },
'links.*.url': { type: 'string' },
'links.*.order': { type: 'int' }
}

Error Outputs

Every library I’ve seen so far is quite rigid in the way they display their errors but that is actually the most important part of a validation library (apart from validating correctly).

We have worked out two different error formats that might come in quite handy and we’ll have a ponder as to how we can improve these even more in the future and even give you a chance to add your custom formatters.

To receive your errors you can simply call the .errors() function on your validation like so:

const validation = Validator.check(formData, rules)
if (validation.failed()) {
const errors = validation.errors()
}

So far we have two different formatters that you can simply access like so:

const asList = errors.asList()
const asSentence = errors.asSentence()

List Output

The first one is called the asList formatter. This one might come in handy if you want to display all errors underneath your input, something like:

+----------------------+
| your input |
+----------------------+
- alphanumerical
- minimum 8 characters
- maximum 20 characters

We’re returning the necessary information only not including anything like ‘should have’ or ‘should be’ — that’s for you to decide.

You can easily concat those rules, display them as a list or do whatever you want with them!

Sentence Output

Yes, the name is a bit weird but this formatter is called asSentence and it generates a proper error string that you can use when you have a lot of space. Assuming your field failed 3 rules, your error string could look something like this:

First name should be alphanumerical, minimum 8 characters and maximum 20 characters

You can use this one to display it in a big red box above the form (or wherever you display validation errors).

Customising field names and error messages

You might want to use the asSentence but you want to customise your field names or the entire error message. Please note, these two options only work with the asSentence formatter.

Custom field names

The way we generate the field name (i.e. “First name”) is generated based on the key in your data object. We split by uppercase characters so if you have something like firstName in your data it would result in First name . This is handy but you might want to change it if you for example have a field called dob which is actually the date of birth. You can easily pass in custom field names into your validation like so:

const validation = Validator.check( formData, rules, {
dob: 'Date of birth'
}

Now the formatter will use the custom field name if a rule for that field fails.

Custom error messages

You might want a proper error message but don’t use our generated string. Similar to the custom field names you can easily pass in a custom error message for a field.

const validation = Validator.check( formData, rules, null, {
firstName: 'First name is required! Must be between 2 - 10 chars'
}

No matter which rule fails now it would always return the specified error message.

Summary

That is it for now! It was good fun writing the library and we’ll be using it in our production system very soon.

I’d love to hear your feedback on the lib (and also the article since it’s my first proper one)!

If you have improvement ideas, feel free to comment or make a PR on github :)

So long!