Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/troygoode/node-tsa

Guard your REST API with a bit of fascism.
https://github.com/troygoode/node-tsa

Last synced: about 6 hours ago
JSON representation

Guard your REST API with a bit of fascism.

Awesome Lists containing this project

README

        

# tsa

*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.

[![NPM](https://nodei.co/npm/tsa.png?downloads=true&stars=true)](https://nodei.co/npm/tsa/)

[![build status](https://secure.travis-ci.org/TroyGoode/node-tsa.png)](http://travis-ci.org/TroyGoode/node-tsa)

## Table of Contents

* [Installation](#installation)
* [Usage](#usage)
* Usage : [Using TSA Directly](#using-tsa-directly)
* Usage : [Using TSA via Express Middleware](#using-tsa-via-express-middleware)
* [Creating Guards](#creating-guards)
* Creating Guards : [Nested Guards](#nested-guards)
* Creating Guards : [Required Properties](#required-properties)
* Creating Guards : [Optional Properties](#optional-properties)
* Creating Guards : [Whitelisting](#whitelisting)
* Creating Guards : [Default Values](#default-values)
* Creating Guards : [Built-In Validations](#built-in-validations)
* Creating Guards : [Custom Validations](#custom-validations)
* Creating Guards : [Transformations](#transformations)
* Creating Guards : [Sanitization](#sanitization)
* Creating Guards : [Rename Properties](#rename-properties)
* Creating Guards : [Combinations](#combinations)
* Creating Guards : [Error Handling](#error-handling)
* [Test](#test)
* [License](#license)
* [Author](#author)

## Installation (via [npm](https://npmjs.org/package/tsa))

```bash
$ npm install tsa
```

## Usage

### Using TSA Directly

Create a guard:

```javascript
var tsa = require('tsa');
var guard = tsa({
property1: tsa.required()
, property2: tsa.optional()
, property3: tsa.default('blah')
});
```

Validate input against guard:

```javascript
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
});
```

### Using TSA via Express Middleware

Create a guard:

```javascript
var tsa = require('tsa');
var guard = tsa({
property1: tsa.required()
, property2: tsa.optional()
});
```

Ensure you're using express's body parser:

```javascript
app.use(express.bodyParser());
```

Add that guard's middleware to your route:

```javascript
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:

```javascript
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
});
```

## Creating Guards

### Nested Guards

```javascript
var address = tsa({
street1: tsa.required()
, street2: tsa.optional()
});
var person = tsa({
name: tsa.required()
, address: address()
});
```

Nested guards can also be created inline:

```javascript
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:

```javascript
// example of adding validations to guard definition
var address = tsa({
street1: tsa.required()
, street2: tsa.optional()
}, {validate: someValidationFunction});
```

```javascript
// example of adding validations to guard usage
var person = tsa({
name: tsa.required()
, address: address({validate: aDifferentValidationFunction})
});
```

### Required Properties

```javascript
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:

```javascript
var guard = tsa({
example1: tsa.property({ required: 'fail!' })
, example2: tsa.required('fail!')
});
```

### Optional Properties

```javascript
var guard = tsa({
property1: tsa.property() // or: tsa.optional()
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result === {}
});
```

### Whitelisting

```javascript
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
});
```

### Default Values

```javascript
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:

```javascript
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
});
```

### Built-In Validations

TSA ships with a few validations built-in. Here are some examples:

```javascript
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() })
});
```
```javascript
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'
}) })
});
```
```javascript
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
});
```

### Custom Validations

```javascript
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:

```javascript
var myValidationFunction = function(input, cb){
if(...){
cb(); // passed!
}else{
cb(['error message 1', 'error message 2']); // failed...
}
};
```

### Transformations

```javascript
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'
});
```

### Sanitization

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:

```javascript
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:

```javascript
var guard = tsa({
foo: tsa.sanitize(tsa.validate.regex(/^bar$/g))
});
```

### Rename Properties

```javascript
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'
});
```

### Combinations

You can combine any and all of the above like so:

```javascript
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'
});
```

### Error Handling

Errors for nested structures are returned like so:

```javascript
[
{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:

```javascript
[
{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:

```javascript
{
'first': ['Required property not provided.']
, 'address[street1]': ['Required property not provided.']
, 'address[zip]': ['Required property not provided.']
}
```

## Test

Run tests via mocha:

```bash
$ npm install -g mocha
$ git clone git://github.com/TroyGoode/node-tsa.git tsa
$ cd tsa/
$ npm install
$ mocha
```

Run example web app:

```bash
$ git clone git://github.com/TroyGoode/node-tsa.git tsa
$ cd tsa/
$ npm install
$ cd example/
$ npm install
$ npm start
$ open http://localhost:3000
```

## License

[MIT License](http://www.opensource.org/licenses/mit-license.php)

## Author

[Troy Goode](https://github.com/TroyGoode) ([[email protected]](mailto:[email protected]))