Guard your REST API with a bit of fascism.
TSA is a node.js library designed to take JSON input and:
- filter it against a whitelist
- validate it
- transform it
- provide default values
It has been designed with usage in an Express-based JSON REST API in mind, and allows you to easily pass it into your route as middleware.
- Installation
- Usage
- Usage : Using TSA Directly
- Usage : Using TSA via Express Middleware
- Creating Guards
- Creating Guards : Nested Guards
- Creating Guards : Required Properties
- Creating Guards : Optional Properties
- Creating Guards : Whitelisting
- Creating Guards : Default Values
- Creating Guards : Built-In Validations
- Creating Guards : Custom Validations
- Creating Guards : Transformations
- Creating Guards : Sanitization
- Creating Guards : Rename Properties
- Creating Guards : Combinations
- Creating Guards : Error Handling
- Test
- License
- Author
Installation (via npm)
$ npm install tsaCreate a guard:
var tsa = require('tsa');
var guard = tsa({
property1: tsa.required()
, property2: tsa.optional()
, property3: tsa.default('blah')
});Validate input against guard:
var input = {
property1: 'foo'
, property4: 'bar'
};
guard().frisk(input, function(err, result){
// err === null
// result.property1 === 'foo'
// result.property2 === undefined
// result.property3 === 'blah'
// result.property4 === undefined
});Create a guard:
var tsa = require('tsa');
var guard = tsa({
property1: tsa.required()
, property2: tsa.optional()
});Ensure you're using express's body parser:
app.use(express.bodyParser());Add that guard's middleware to your route:
app.post('/foo', guard().middleware(), function(req, res){
// req.body is the whitelisted, validated, transformed version of the input from req.body
});
app.error(function(err, req, res, next){
// err is an array of errors generated by the guard
});Alternatively you can handle the errors on a per-route basis instead of globally:
app.post('/foo', guard().middleware(function(err, req, res, next){
// return a 400, show an error page, ignore by calling next, whatever
}), function(req, res){
// req.body is the whitelisted, validated, transformed version of the input from req.body
});var address = tsa({
street1: tsa.required()
, street2: tsa.optional()
});
var person = tsa({
name: tsa.required()
, address: address()
});Nested guards can also be created inline:
var person = tsa({
name: tsa.required()
, address: tsa({
street1: tsa.required()
, street2: tsa.optional()
})()
});You can validate/transform/etc nested guards either at the definition level, or the usage level:
// example of adding validations to guard definition
var address = tsa({
street1: tsa.required()
, street2: tsa.optional()
}, {validate: someValidationFunction});// example of adding validations to guard usage
var person = tsa({
name: tsa.required()
, address: address({validate: aDifferentValidationFunction})
});var guard = tsa({
property1: tsa.property({ required: true }) // or: tsa.required()
});
var input = {};
guard().frisk(input, function(err, result){
// err === instanceof Array
// err[0] === {key: 'property1', error: 'Required property property1 not supplied.'}
});You can provide a custom error message like so:
var guard = tsa({
example1: tsa.property({ required: 'fail!' })
, example2: tsa.required('fail!')
});var guard = tsa({
property1: tsa.property() // or: tsa.optional()
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result === {}
});var guard = tsa({
property1: tsa.required()
});
var input = {
property1: 'foo'
, property2: 'bar'
};
guard().frisk(input, function(err, result){
// result.property1 === 'foo'
// result has no property2 key
});var guard = tsa({
foo: tsa.property({ default: 'bar' }) // or: tsa.default('bar')
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result.foo === 'bar'
});Optionally, the default value can be a function which will be executed by tsa:
var now = function(){
return new Date();
};
var guard = tsa({
foo: tsa.property({ default: now }) // or: tsa.default(now)
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result.foo === a Date object
});TSA ships with a few validations built-in. Here are some examples:
var guard = tsa({
foo: tsa.require({ validate: tsa.validate.boolean() })
, bar: tsa.require({ validate: tsa.validate.boolean('The value "%1" is not a boolean.') }) // <- custom error message
, baz: tsa.require({ validate: tsa.validate.true() })
, boo: tsa.require({ validate: tsa.validate.false() })
});var guard = tsa({
foo: tsa.require({ validate: tsa.validate.numeric() })
, foo2: tsa.require({ validate: tsa.validate.numeric('fail!') }) // <- custom error message
, bar: tsa.require({ validate: tsa.validate.range(0, 10) })
, bar2: tsa.require({ validate: tsa.validate.range(0, 10, {
invalid: 'custom error message'
, below: 'custom error message'
, above: 'custom error message'
}) })
, baz: tsa.require({ validate: tsa.validate.min(0) })
, baz2: tsa.require({ validate: tsa.validate.min(0, {
invalid: 'custom error message'
, below: 'custom error message'
}) })
, boo: tsa.require({ validate: tsa.validate.max(10) })
, boo2: tsa.require({ validate: tsa.validate.max(10,
invalid: 'custom error message'
, above: 'custom error message'
}) })
});var guard = tsa({
foo: tsa.require({ validate: tsa.validate.regex(/^bar$/g) })
, foo: tsa.require({ validate: tsa.validate.regex(/^bar$/g, 'fail!') }) // <- custom error message
});var mustBeUpper = function(input, cb){
if(input.toUpperCase() === input){
cb(); // yes, this is uppercase
}else{
cb('not uppercase!'); // oh noes!
}
};
var guard = tsa({
foo: tsa.property({ validate: mustBeUpper }) // or: tsa.validate(mustBeUpper)
});
var input = { foo: 'bar' };
guard().frisk(input, function(err, result){
// err[0] === {key: 'foo', error: 'not uppercase!'}
// result === null
});Your custom validations can return multiple errors, if necessary:
var myValidationFunction = function(input, cb){
if(...){
cb(); // passed!
}else{
cb(['error message 1', 'error message 2']); // failed...
}
};var toUpper = function(input, cb){
cb(null, input.toUpperCase());
};
var guard = tsa({
foo: tsa.property({ transform: toUpper }) // or: tsa.transform(toUpper)
});
var input = { foo: 'bar' };
guard().frisk(input, function(err, result){
// err === null
// result.foo === 'BAR'
});Sanitizing a property runs a validation function against it, but rather than failing the guard if an error is reported that property is simply thrown away in the case of an error:
var mustBeUpper = function(input, cb){
if(input.toUpperCase() === input){
cb(); // yes, this is uppercase
}else{
cb('not uppercase!'); // oh noes!
}
};
var guard = tsa({
foo: tsa.property({ sanitize: mustBeUpper }) // or: tsa.sanitize(mustBeUpper)
, fizz: tsa.sanitize(mustBeUpper)
});
var input = { foo: 'bar', fizz: 'BANG' };
guard().frisk(input, function(err, result){
// err === null
// result.foo === undefined
// result.fizz === 'BANG'
});Note that you can run TSA's built-in validations through sanitize:
var guard = tsa({
foo: tsa.sanitize(tsa.validate.regex(/^bar$/g))
});var guard = tsa({
foo: tsa.property({ rename: 'bar' }) // or: tsa.rename('bar')
});
var input = { foo: 'blah' };
guard().frisk(input, function(err, result){
// result.foo === undefined
// result.bar === 'blah'
});You can combine any and all of the above like so:
var toUpper = function(input, cb){
cb(null, input.toUpperCase());
};
var guard = tsa({
foo: tsa.property({ required: true, transform: toUpper })
// or: tsa.required({ transform: toUpper })
// or: tsa.transform(toUpper, {required: true})
});
var input = { foo: 'bar' };
guard().frisk(input, function(err, result){
// err === null
// result.foo === 'BAR'
});Errors for nested structures are returned like so:
[
{key: 'first', error: 'Required property not provided.'}
, {key: 'address', error: [
{key: 'street1', error: 'Required property not provided.'}
, {key: 'zip', error: 'Required property not provided.'}
]}
]While this is a very structured format, it isn't always the easiest for
doing things like highlighting form fields that have errors. In those
situations you can pass the error structure into the tsa.flattenErrors
method to get back something like this:
[
{key: 'first', error: 'Required property not provided.'}
, {key: 'address[street1]', error: 'Required property not provided.'}
, {key: 'address[zip]', error: 'Required property not provided.'}
]Passing {hash: true} into tsa.flattenErrors as the second argument results in:
{
'first': ['Required property not provided.']
, 'address[street1]': ['Required property not provided.']
, 'address[zip]': ['Required property not provided.']
}Run tests via mocha:
$ npm install -g mocha
$ git clone git://github.com/TroyGoode/node-tsa.git tsa
$ cd tsa/
$ npm install
$ mochaRun example web app:
$ git clone git://github.com/TroyGoode/node-tsa.git tsa
$ cd tsa/
$ npm install
$ cd example/
$ npm install
$ npm start
$ open http://localhost:3000
