Valid Typescript Data Guaranteed

Photo by John Doyle on Unsplash

My Aim

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.

Tools

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:

Describing data using Joi

Validation would happen such as in:

Validating data using Joi

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.

The Abstractions

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:

Primitive

Now, to go back to our previous example, a primitive for a person’s age would be:

Primitive Subclassing

With const personAgeSchema = Joi.number().min(1).

Now we have:

Validating Primitives

The ValidObject Abstraction

The abstraction used to model compound objects is calledValidObject:

ValidObject Class

The constructor takes a JoiSchemauseful 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 Primitive<PrimitiveType>orValidObject :

Argument Descriptors

In the body of the constructor, there’s a call to this._init:

__init

Here, I set the properties of the object, based on the given descriptors:

this.setProperties(...x)

Then, I generate an object that is the one that will be validated by Joi:

this._value= this.getValidable() .

The feisty lookingthis.setProperties is the heart of ValidObject:

In a nutshell, for each ArgumentDescriptor , I initialize a private counterpart and I give it the appropriate getter and 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:

buildValidObject

Example Usage

Conclusion

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:

Example with Factory Function

Thanks for reading ! You can find the git repo here.

Solutions Architect and Coder

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store