When writing back-end NodeJS APIs in Typescript, I don’t want to worry about the validity of the data my services are consuming or producing. Given I have correctly captured the business rules and the data validity criteria, I want to be able to trust that the data I am working with is always valid.
The basic tool I will use here are Joi and a set of abstractions I will present in coming paragraphs :
Joi provides an API to describe your data as well as the validation rules that go with.
For example, say I wanted to describe and be able to validate a person object. Each person object having a name and an age properties. The name must be a non-empty string of minimum 3 characters, while the age is a number that cannot be less than 1. With Joi, this would be described as:
Validation would happen such as in:
Before going further, you can find the code mentioned below at https://github.com/kanian/ts-valid-object . Now let’s talk about the abstractions I need now.
We can envision the data the application uses as, either primitives or compound objects. Compound objects are made of primitives or other compound objects.
The Primitive Abstraction
In Typescript, The most basic piece of data I could ever come across is a literal that is either a string, a boolean, a number or a symbol. We could summarize that into a union type
type PrimitiveType = boolean | string | number | symbol.
The abstraction, which I call a
Primitive, hosts a
PrimitiveType.If the value of the
PrimitiveTypedoes not follow the validation rules stipulated by its schema, then the creation of the
Primitive must fail:
Now, to go back to our previous example, a primitive for a person’s age would be:
const personAgeSchema = Joi.number().min(1).
Now we have:
The ValidObject Abstraction
The abstraction used to model compound objects is called
The constructor takes a Joi
Schemauseful for validation. It also takes a variable number of
ArgumentDescriptors that define the properties that exist on the soon to be created object. The properties described can extend
In the body of the constructor, there’s a call to
Here, I set the properties of the object, based on the given descriptors:
Then, I generate an object that is the one that will be validated by Joi:
this._value= this.getValidable() .
The feisty looking
this.setProperties is the heart of ValidObject:
In a nutshell, for each
ArgumentDescriptor , I initialize a private counterpart and I give it the appropriate
setter . The
setter will consume a
PrimitiveType or a
ValidObject , and resync the value used for validation by Joi.
It is worth noting that when the setter’s value is a validObject, then, I recursively build that ValidObject. This ensures that the whole structure is valid:
The best way for me to use this approach is to first define what my data types are and the validation rules that constrain these data types. Joi let’s us do that easily. I would then build factory functions for all the primitive and compound objects I need. Then usage can be exemplified in the following diagram:
Thanks for reading ! You can find the git repo here.