Using Validate.js via a fluent TypeScript interface

When it comes to user input validation, particularly when designing (RESTful, GraphQL) APIs, it’s important to have these objectives in mind:

  • Validator needs to support various data types such as emails, mobile phone numbers in different locales, credit cards, etc. Well tested and already used in production by many companies.
  • It needs to have an API (interface) that allows writing validation rules with the minimum amount of boilerplate code.
  • This interface needs to be strongly typed, allowing a better development experience (DX) when used with TypeScript.

I think validator.js (✭17k) fits perfectly into the first requirement. And, in order to make it less verbose, I would suggest using the following API (interface):

import { validate } from "validator-fluent";const input = {
givenName: "John",
familyName: "Doe",
email: "john@example.com",
age: "18",
website: "",
};
const [data, errors] = validate(input, (value) => ({
given_name: value("givenName")
.notEmpty()
.isLength({ min: 3, max: 25 }),
family_name: value("familyName")
.notEmpty()
.isLength({ min: 1, max: 25 }),
email: value("email")
.notEmpty()
.isEmail(),
age: value("aga").toNumber(),
website: value("website").isURL(),
});
if (Object.keys(errors).length === 0) {
await db.table("customer").insert(data);
}

Validation Only (mode)

Often, it also needs to be possible to validate user input without saving it into the database. Example:

const input = {
email: "john@example.com"
};
// Validate user input and exit
const dryRun = true;
const [data, errors] = validate(input, (value) => ({
email_address: value("email")
.notEmpty({ if: !dryRun })
.isEmail(),
});
if (!dryRun && Object.keys(errors).length === 0) {
await db.table("customer").insert(data);
}

Adding Custom Rules

You can extend the built-in validator class with any additional validation rules (methods).

import { Validator } from "validator-fluent";export class CoolValidator<K, V> extends Validator<K, V> {
constructor(key: K, value: V) {
super(key, value);
}
isLegit(): this {
if (!this.isEmpty && this.value !== "legit") {
this.errors.push("Not legit.");
}
return this;
}
}
// -----------------------------------------------------------------import { validate, ValidationError } from "validator-fluent";const input = { name: "???" };const [data, errors] = validate(input, CoolValidator, (value) => ({
name: value("name").notEmpty().isLegit(),
}));
if (Object.key(errors).length > 0) {
throw new ValidationError(errors);
}

References

Bringing the technical edge to early-stage companies with advice on software architecture, best practices, database design, web infrastructure, and DevOps.

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