Things get even tougher when you need to make a change — you have to find every possible form in the client that’s accessing the same server API and modify its code, so it fits the new API. The same validations need to be maintained again, and again, and again.
And of course, there are cases in which there are bugs, the implementations are not really identical, or that you just, simply, miss something.
With dozens of forms and API routes, we had to find an elegant solution to this problem.
Around two years ago, in one of Fiverr’s weekly tech discussions, the idea of isomorphic validations was mentioned. That’s the day Passable was born.
When starting to work on Passable, a few guidelines were put in place:
- Validations have to be isomorphic, so they can run both on the server and in the client.
- Validation logic has to be reusable
- Validations should be separate from the app’s logic and run as standalone modules, so they do not end up getting tangled into the code.
- Validations have to be declarative, easy to read and write.
- New validation rules may be added as extensions, not requiring to publish a new version of the whole system.
- The system needs to be flexible and support special cases of data model validations:
- Non-required fields (that still get validated if filled)
- AND, NOT and OR relationships between different constraints
- Warn-only tests (password strength, for example)
- Running a single test at a time out of a whole spec (for onchange() events, for example).
It is also important to acknowledge the distinction between two kinds of validations:
- Product validations
- Technical validations
Product validations are any product specifications, describing the desired look, feel and behavior of the product — both when saving information or displaying it.
Technical validations are any other constraint that take into consideration security, data sanitization and integrity.
We decided that in our first implementation, our solution would only handle product validations— since they are safe to perform on the browser level, and do not reveal internal business logic to the outside world. This leaves the server-only validations focused on security rather than model validations.
After multiple tests and experiments, we came up with a structure that tries to capture the style and spirit of unit tests. Describing our data model as a spec that we need to run the data against gives us readability and modularity.
The following chunk of code is a very basic example of the solution we came up with. With just a glance, you can see all parts of the form that get validated, the rules of each field and even the errors produced when the validations fail.
Currently at Fiverr, we have Passable running both for server and client side validations, and the process is pretty straightforward. We have a Passable node.js server set up with the required validation specs. Those specs are then accessible in two different ways:
- Through POST routes that the server exposes
- Through JS files that are being served statically from the server for client side consumption
When browsing to a page with a Passable enabled form, the browser downloads the spec file from the server. When submitting the form, the data flows through our Passable server for validation and is subjected to exactly the same tests it would encounter in the client.
After playing around with Passable and its capabilities, we discovered an unexpected use case. Since data model specs are separated from the actual view logic, data model validation can also be performed when pulling information back from the server. This means that you can handle corrupt data & provide fallbacks even for information saved during periods in which the validation criteria were different.
We’re opening Passable up to the community in the hope that others might find it useful and may be interested in contributing to future development as well.
Further documentation for Passable can be found in: