arrest
Version:
OpenAPI v3 compliant REST framework for Node.js, with support for MongoDB and JSON-Schema
287 lines (224 loc) • 8.03 kB
Markdown
arrest
======
Swagger REST framework for Node.js, with support for MongoDB and [JSON-Schema](http://json-schema.org/)
[](https://travis-ci.org/vivocha/arrest)
[](https://coveralls.io/github/vivocha/arrest?branch=master)
[](https://www.npmjs.com/package/arrest)
Arrest lets you write RESTful web services in minutes. It automatically generates a [Swagger](http://swagger.io/) 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
```bash
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*:
```js
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:
```bash
curl "http://localhost:3000/tests"
```
You can add a new item:
```bash
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):
```bash
curl "http://localhost:3000/tests/51acc04f196573941f000002"
```
You can update an item:
```bash
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:
```bash
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:
```js
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:
```js
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:
```js
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 `GET`s 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](http://swagger.io/specification/). For instance,
the following code show how to validate the body of a `POST` and the query paramters of a `GET`:
```js
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 `POST`ing to
`http://localhost/my-resources` will return an error. Likewise `GET`ting 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**