“Only the paranoid survive.” – Andy Grove

No software developer in their right mind would trust user inputs in an application. All user inputs need to be validated and sanitized, otherwise nasty security vulnerabilities like SQL injection and Cross-Site Scripting (XSS) attacks might happen. Once user inputs pass validation and start to go through internal layers of code, many developers assume that there isn’t much need to validate inputs anymore, but validation isn’t just about security, it’s also about correctness.

Many bugs are caused by passing the wrong type of data to a function such as passing a string when an integer is expected or passing an array with fewer fields than necessary. Language features like static type checking is good at detecting many type errors during compilation, but can’t catch everything in runtime like Java’s infamous NullPointerException.

As for TypeScript, which adds type annotations to JavaScript, there are no runtime checks at all after the code is transpiled to JavaScript. Even if you have type annotations for your entire code base, any inputs coming from a database or an external API can still be of a wrong type.

One of the most effective methods for detecting invalid types is to validate function arguments in runtime. Validating every single argument can get tedious fast though, with line after line of if statements. Fortunately, there are some good data validation libraries out there like Joi to make data validation easier, but it can still get long-winded at times, and you wouldn’t really want to give developers any excuse to skip data validation :)

Instead of using an all-purpose validation library designed for validating a wide range of data types, it might make more sense to use a library tailor-made to validate function arguments. With that idea in mind, I’ve created a lightweight JavaScript function argument validation library called fn-arg-validator. Here’s a quick example of how it works:

const is = require('fn-arg-validator');

function createUser(firstName, lastName, birthDate) {
	is.valid(is.string, is.string, is.date, arguments);
	// ...
}

createUser('Thomas', 'Anderson', '1971-09-13');
// [WARN] 1971-09-13 failed date check 

I’ve decided to use a functional style interface where you pass type check functions (built-in or your own) for each function argument, and as the final argument, you simply pass the arguments object to avoid typing the function parameters again.

When validating arrow function arguments, the arguments object isn’t available, so you need to provide the parameter names in an array like [firstName, lastName, birthDate].

The example code above will normally log a warning and continue to run, but you may choose to check the return value of is.valid to stop the execution of the rest of the code. The decision depends on how you want to handle type errors. Sometimes, a warning is enough, and sometimes you need to be strict and throw an error.

Boundary Checks and Maybe Types

In addition to strict type checks, it’s possible to do things like string length checks, and use maybe types that also accept undefined/null values:

function createUser(firstName, lastName, birthDate) {
	is.valid(is.stringBetween(1, 20), is.stringLTE(20), is.maybeDate, arguments);
	// ...
}

Object Property and Type Checks

is.objectWithProps can be used to check if an object has the specified properties and those properties have the correct types.

const userObjectProps = { id: is.number, firstName: is.string, lastName: is.string, birthDate: is.date };

function updateUser(user) {
    if (!is.valid(is.objectWithProps(userObjectProps), arguments)) {
        throw new Error('Invalid user object');
    }
    // ...
}

updateUser({ id: 1, firstName: 'Thomas', lastName: 'Anderson', birthDate: '1971-09-13' });
/*
[WARN] {"id":1,"firstName":"Thomas","lastName":"Anderson","birthDate":"1971-09-13"} failed objectWithProps check

Error: Invalid user object
...
*/

You can have one-level of nested object property checks as shown below:

is.valid(
    is.objectWithProps({
        a: is.number,
        b: is.objectWithProps({ c: is.string, d: is.number }),
    }),
    arguments
);

Passing your Own Type Check Functions

The beauty of a functional style interface is that you aren’t limited to the built-in validation functions, you can simply pass your own. The only requirement is to give your functions names since is.valid uses function names for logging purposes.

Log Configuration

By default, fn-arg-validator uses the console object for logging. However, this can be configured by assigning a different logger to is.config.log.

The log level can be set by changing the value of is.config.logLevel. The default log level is ‘WARN’, which only logs failed checks. If you would like to see successful validations, you need to set the log level to ‘DEBUG’ or higher. To disable all logging, set the log level to ‘OFF’.


Finally, to see the complete list of built-in functions, you can take a look at fn-arg-validator’s GitHub page.


Related: