node package manager

arrest

arrest

Swagger REST framework for Node.js, with support for MongoDB and JSON-Schema

travis build Coverage Status npm version

Arrest lets you write RESTful web services in minutes. It automatically generates a Swagger description of the API and support input validation using JSON-Schemas.

Highlight features:

  • Compatible with Express 4.x
  • Implements simple CRUD semantics on MongoDB
  • Supports querying object with the RQL syntax
  • Input validation with JSON-Schema
  • Oauth2 scope checks per operation

Note for 1.3 users: arrest 3.x is a complete rewrite of the old module and it's not backwards compatible.

How to Install

npm install arrest

Super Simple Example

The following sample application shows how to create a simple REST API, using a MongoDB collection as the data store. In the sample, the path /tests is linked to a tests collection on a MongoDB instance running on localhost:

const arrest = require('arrest');
const api = new arrest.API();
 
api.addResource(new arrest.MongoResource('mongodb://localhost:27017', { name: 'Test' }));
 
api.listen(3000);

The Swagger specification of the API you just created is available at http://localhost:3000/swagger.json

Now you can query your data collection like this:

curl "http://localhost:3000/tests"

You can add a new item:

curl "http://localhost:3000/tests" -H "Content-Type: application/json" -X POST -d '{ "name": "Jimbo", "surname": "Johnson" }'

You can query a specific item by appeding the identifier of the record (the _id attribute):

curl "http://localhost:3000/tests/51acc04f196573941f000002"

You can update an item:

curl "http://localhost:3000/tests/51acc04f196573941f000002" -H "Content-Type: application/json" -X PUT -d '{ "name": "Jimbo", "surname": "Smith" }'

And finally you can delete an item:

curl "http://localhost:3000/tests/51acc04f196573941f000002" -X DELETE

Creating an API

An API is a collection of Resources, each supporting one or more Operations.

In arrest you create an API by creating an instance of the base API class or of a derived class. You then add instances of the Resource class or a derived one. Each resource contains its supported Routes, that is a collection of instances of classes derived from the abstract Operation, which represents an operation to be executed when an HTTP method is called on a path.

The following code demonstrates this three level structure:

const arrest = require('arrest');
const api = new arrest.API();
 
const operation1 = function(req, res, next) {
  res.json({ data: 'this is operation 1' });
}
const operation2 = function(req, res, next) {
  res.json({ data: 'this is operation 2' });
}
const operation3 = function(req, res, next) {
  res.json({ data: 'this is operation 3' });
}
const resource1 = new arrest.Resource({
  name: 'SomeResource',
  routes: {
    '/': {
      get: operation1,
      post: operation2
    },
    '/some-path': {
      put: operation3
    }
  }
})
 
api.addResource(resource1);
api.listen(3000);

The API above supports the following operations:

  • GET on http://localhost/some-resources
  • POST on http://localhost/some-resources
  • PUT on http://localhost/some-resources/some-path

Please note how the some-resources path was automatically constructed using the name of the resource SomeResource, making it plural and converting the camelcase in a dash-separated name. This default behaviour can be changed specifying the namePlural and path when creating the resource (e.g. new Resource({ name: 'OneResource', namePlural: 'ManyResources', path: 'my_resources' }))

Another other way to produce the same result is:

const arrest = require('arrest');
const api = new arrest.API();
 
const resource1 = new arrest.Resource({ name: 'SomeResource' });
 
resource1.addOperation('/', 'get', function(req, res, next) {
  res.json({ data: 'this is operation 1' });
});
resource1.addOperation('/', 'post', function(req, res, next) {
  res.json({ data: 'this is operation 2' });
});
resource1.addOperation('/some-path', 'put', function(req, res, next) {
  res.json({ data: 'this is operation 3' });
});
 
api.addResource(resource1);
api.listen(3000);

In real world applications, where resources and operation are in fact more complex, you will want to create class that extend the basic classes in arrest, like in the next example:

const arrest = require('arrest');
 
class MyOperation extends arrest.Operation {
  constructor(resource, path, method) {
    super('op1', resource, path, method);
  }
  handler(req, res, next) {
    res.json({ data: 'this is a custom operation' });
  }
}
 
class MyResource extends arrest.Resource {
  constructor() {
    super();
    this.addOperation(new MyOperation(this, '/', 'get'));
  }
}
 
class MyAPI extends arrest.API {
  constructor() {
    super({
      info: {
        title: 'This is a custom API',
        version: '0.9.5'
      }
    });
    this.addResource(new MyResource());
  }
}
 
const api = new MyAPI();
api.listen(3000);

The API above supports GETs on http://localhost/my-resources (note how the path was in this case constructed automatically from the name of the class MyResource).

By the default, arrest APIs add a special route /swagger.json that returns the Swagger description of the API: the Swagger object is populated with the properties of the API object, Resources are converted into Swagger Tags and Operations are mapped to Swagger Operations.

Data validation

arrest supports JSON-Schema for data validation. Validation rules are set using the Swagger specification. For instance, the following code show how to validate the body of a POST and the query paramters of a GET:

class MyOperation1 extends arrest.Operation {
  constructor(resource, path, method) {
    super('op1', resource, path, method);
    this.setInfo({
      parameters: [
        {
          name: 'body',
          in: 'body',
          required: true,
          schema: {
            type: 'object',
            required: [ 'name' ],
            additionalProperties: false,
            properties: {
              name: {
                type: 'string'
              },
              surname: {
                type: 'string'
              }
            }
          }
        }
      ]
    });
  }
  handler(req, res, next) {
    res.json({ data: 'this is a op1' });
  }
}
 
class MyOperation2 extends arrest.Operation {
  constructor(resource, path, method) {
    super('op2', resource, path, method);
    this.setInfo({
      parameters: [
        {
          name: 'lang',
          in: 'query',
          type: 'string',
          required: true
        },
        {
          name: 'count',
          in: 'query',
          type: 'integer'
        }
      ]
    });
  }
  handler(req, res, next) {
    res.json({ data: 'this is a op2' });
  }
}
 
class MyResource extends arrest.Resource {
  constructor() {
    super();
    this.addOperation(new MyOperation1(this, '/', 'post'));
    this.addOperation(new MyOperation2(this, '/', 'get'));
  }
}

Omitting the body or passing an invalid body (e.g. an object without the name property) when POSTing to http://localhost/my-resources will return an error. Likewise GETting without a lang parameter or with a count set to anything other than a number will fail.

Scopes and security validators

TBA

Creating an API with a MongoDB data store

TBA (default api routes)

Using arrest with express

TBA

Debugging

TBA

API documentation

TBA