restify-hapi v1.5.0
Restify-Hapi - Fast and simple RESTful API builder for Hapi
A RESTful API generator plugin for the hapi framework utilizing the mongoose ODM, hapi-swagger and joi.
Super simple and yet very powerful
restify-hapi is designed to be the simplest and fasted way possible to make RESTful APIs out of a mongoose schema definition. It is a hapi plugin intended to abstract the work involved in setting up API routes/validation/handlers/documentation/etc. for the purpose of fast app development without boilerplate code.
Define your (mongoose) schema for your resource only once and reuse it (for joi). This enforces a strict and explicit API definition which, together with hapi-swagger, evaluates in a very detailed API documentation.
Table of contents
- Installation
- Example
- Default Routes
- Supported Data Types
- Pagination
- Query Pipeline
- Aggregation Pipeline
- Configuration Options
- Testing
- Swagger
- Tools
Installation
This module was implemented and tested with:
- NPM version 3.10.8
- node version 4.x
- mongodb version 3.x
- mongoose version 4.x
I do not know how this module will behave with other configurations.
$ npm install restify-hapiExample
This is the example we use in the test hapi-server.
// first require all mongoose models because they might be referenced
// in the restify method
require("./fixtures/User");
require("./fixtures/Company");
const options = {
User: {
routes: {
findAll: {
populate: false
}
}
},
Company: {
single: "company",
multi: "companies",
hasMany: [
// employees (users) are destroyed when this company is destroyed
// however in this case we apply the archive policy. This means
// that we do not remove the resources but mark them as _archived=true
{
fieldName: "employees",
destroy: true,
archive: {
enabled: true
}
}
]
}
};
server.route(Restify.restify(options, Logger));If you enable the archive feature then you have to make sure that your mongoose schemas specify the used archive attribute field (per default this is set to _archived)
Default Routes
| Method | Path | Information |
|---|---|---|
| GET | /resources | list all resources (Pagination, Query-Pipeline and Aggregation Pipeline are available) |
| GET | /resources/{id} | list resource details (Aggregation Pipeline is available) |
| POST | /resources | create a new resource |
| PUT | /resources/{id} | update a resource |
| PUT | /resources | bulk update multiple resources |
| DELETE | /resources/{id} | delete a resource |
| DELETE | /resources | bulk delete multipe resources |
Supported Data Types
The User Schema summarizes all supported data-types and validations.
const UserSchema = new Schema({
name: { type: String, match: /.*/ },
email: { type: String, minlength: 3, maxlength: 20, required: true, default: "example@user.com"},
binary: { type: Buffer, required: true, default: "default buffer" },
living: { type: Boolean, required: true, default: false },
password: { type: String, required: true, minlength: 8 },
updated: { type: Date, default: now, min: dummyDate, required: true },
age: { type: Number, min: 18, max: 65, required: true, default: 20 },
mixed: { type: Schema.Types.Mixed, required: true, default: "test", enum: ["test", "test1", "test2"] },
array: [],
arrayTwo: { type: Array, required: true, min: 2, max: 5 },
ofString: [String],
ofNumber: { type: Array, required: true, min: 2, default: [1, 2] },
ofDates: [Date],
ofBuffer: [Buffer],
ofBoolean: [Boolean],
ofMixed: { type: [Schema.Types.Mixed], min: 2},
ofObjectId: [Schema.Types.ObjectId],
nested: {
stuff: { type: String, lowercase: true, trim: true },
otherStuff: { type: Number, required: true }
},
company: { type: Number, ref: "Company", required: true },
_archived: { type: Boolean, required: true, default: false }
});Pagination
Pagination works with the limit and offset parameters
limitspecify how many results to displayoffsetspecify at which position to start fetching the data in mongodb (skip)
Example:
?limit=10&offset=5 skipps the first 5 documents and fetches the next 10 in the mongodb collection
Query Pipeline
restify-hapi provides query-paremeters out-of-the-box for almost every mongoose data type. The model attributes are suffixed with Query. You can query as following:
single valuee.g. nameQuery=Johnarray valuee.g. nameQuery="John", "Doe"valid mongodb querye.g. nameQuery={"name": {"$eq": "John"}}
The query pipeline is not available for all routes. Please refere to Default Routes for more information.
Aggregation Pipeline
restify-hapi also provides an interface to select only certain fields and to sort by fields
sortcomma separated values (prefix with-for dsc sort). E.g. sort=-archived,nameprojectselect only a subset of fields. Works the same way as sort
Configuration Options
The default options (in this case for the sample company resource) are as follows. You can overwrite all attribute values as long as they are valid for a hapi-server and as long as they conform to the joi-schema specification of the config object defined in baseConfig).
{
"single": "company",
"multi": "companies",
"hasMany": [
{
"fieldName": "employees",
"destroy": true,
"archive": {
"enabled": true,
"attribute": "_archived"
}
}
],
"model": "Company",
"routes": {
"findAll": {
"enabled": true,
"method": "GET",
"path": "/api/v1/companies",
"description": "Fetch all companies",
"notes": "Returns a list of all companies",
"populate": false,
"skipRequired": false,
"skipInternals": true,
"skipId": false
},
"findOne": {
"enabled": true,
"method": "GET",
"path": "/api/v1/companies/{id}",
"description": "Find this company",
"notes": "Returns the company object belonging to this id",
"populate": true,
"skipRequired": false,
"skipInternals": false,
"skipId": false
},
"create": {
"enabled": true,
"method": "POST",
"path": "/api/v1/companies",
"description": "Create a new company",
"notes": "Returns the newly created company object",
"password": {
"validate": true,
"encrypt": true,
"minlength": 8,
"numbers": true,
"uppercase": true,
"special": true
},
"skipRequired": false,
"skipInternals": true,
"skipId": true
},
"update": {
"enabled": true,
"method": "PUT",
"path": "/api/v1/companies/{id}",
"description": "Update this company",
"notes": "Returns the updated company object",
"password": {
"validate": true,
"encrypt": true,
"minlength": 8,
"numbers": true,
"uppercase": true,
"special": true
},
"skipRequired": true,
"skipInternals": true,
"skipId": true
},
"bulkUpdate": {
"enabled": true,
"method": "PUT",
"path": "/api/v1/companies",
"description": "Update a set of companies",
"notes": "Returns the list of updated companies",
"password": {
"validate": true,
"encrypt": true,
"minlength": 8,
"numbers": true,
"uppercase": true,
"special": true
},
"skipRequired": true,
"skipInternals": true,
"skipId": false
},
"delete": {
"enabled": true,
"method": "DELETE",
"path": "/api/v1/companies/{id}",
"description": "Remove this company",
"notes": "Returns http status of this action",
"skipRequired": false,
"skipInternals": true,
"skipId": false
},
"bulkDelete": {
"enabled": true,
"method": "DELETE",
"path": "/api/v1/companies",
"description": "Remove a set of companies",
"notes": "Returns http status of this action",
"skipRequired": false,
"skipInternals": true,
"skipId": false
}
},
"auth": false,
"tags": [
"api"
],
"prefix": "/api/v1",
"populate": true,
"destroy": true,
"archive": {
"enabled": true,
"attribute": "_archived"
}
}singlehow this resource's single name should be (per default derived from the mongoose schema name)multihow this resource's multi name should be (per default single name + s)hasManyspecify the has-many relationship options (this is optional. You can specify references without specifying any options here. In this case the destroy is set to false)fieldNamethe attribute name in the mongoose schema which references the reference modeldestroywhether to remove the referenced resources if this resource is removed (default is false)archive(only works if destroy is set to true)enabeldwhether mark the resource asarchivedor to remove it from the collection upon removalattributeif archive policy is enabled the you can specify the attribute field name which should be used in the schema to mark this resource as archived (default is _archived). You have to make sure that the specified attribute field name exists on the schema, otherwise the library will throw an error
routesRESTful CRUD operations for this resourceenabledwhether to use this route or not (default set to true)passwordsettings for the password attribute in the payload of this routeskipXXXyou should not alter this settingsauthauthentication settings on route-layer
authauthentication settings on root-layer (will be applied for routes which have no auth settings specified). Must be a valid hapi authentication settingprefixused as api prefixpopulatewhether to populate reference resources or notarchiveglobal archive policy
Testing
Clone the package, install the dependencies, and then run:
$ npm test // runs the unit tests
$ npm run test-cover // module test-coverage report
$ npm run test-server // starts the test hapi-server located under ./tests/server.jsSwagger
You can use hapi-swagger to document the auto-generated API. All restified resources will have a very detailed swagger api documentation.
Exapmle configuration:
const swaggerOptions = {
info: {
"title": "API Documentation",
"version": Pack.version
},
"basePath": "/api",
"pathPrefixSize": 3
};
server.register([
Inert,
Vision,
{
"register": HapiSwagger,
"options": swaggerOptions
}
], next);Tools
Pagination, query pipeline, and aggregation pipeline make use of the modules stored in the tools folder. You can use these tools also for resources which are not restified and which are manually added to the hapi-srever. These ensures that you paging, querying and all aggregations work the same way for those resources aswell. Please refere to the code for doc.
| Module | Method | Description |
|---|---|---|
| Enhancer | addMetaCollection | adds the links (first, prev, self, next, last), total, and count information to a collection |
| Enhancer | addMetaResource | adds the links (self) information to a single resource |
| Parser | parse | parses the offset, limit, project, sort, and queries parameters |
| Security | hashPassword | hash the given password with bcrypt |
| Security | comparePasswords | compare a password with a hashedPassword for equality |
| Validator | validatePassword | validates the given password against the specified options |
| Validator | generateRandomPassword | generates a sample password |
