Simple example project is available at https://github.com/vedi/restifizer-example.
Full functional seed-project based on MEAN.JS (http://meanjs.org/) is available at https://github.com/vedi/restifizer-mean.js.
If you are looking for Restifizer SDK for Unity3d, you can find it here: https://github.com/vedi/restifizer-unity3d
Restifizer - it's a way to significantly simplify creation of full-functional RESTful services. Access to the data is carried out via plug-in data sources: restifizer-mongoose-ds and restifizer-sequelize-ds. The data source modules are designed on top of the Mongoose and the Sequelize respectively, which allows us to use such databases as the MongoDB, MSSQL, MySQL, MariaDB, PostgreSQL and SQLite.
The key feature of the restifizer
- it's coupled to supported ORMs as result it allows to make implementing of the
services as soon as it's possible.
Wide list of the features of these ORMs becomes available in your server out of the box.
There is a list of some of these features:
- querying engine - it literally means you can define MongoDB/Sequelize queries in your http-requests,
- nested objects and arrays - since resources are just MongoDB docs, you can easily use any nested structures supported,
- data population - you can easily define what the additional data should fetched and populated (JOIN) in your resource.
Such features on one hand allow your service to be developed extremely fast, but on other hand you always should remember, you need to solve all possible performance impacts before coming production. For example you should allow filtering by indexed fields only. By default it's allowed for each ones.
It's a core of RESTful services. Resources are available at a specific path on the server. For example: "/api/users"
.
In the best traditions of REST-genre the most of the actions with resource can be "expressed" with http-methods.
restifizer
supports following methods:
- GET - selects a set of resources or a resource if a key is specified,
- HEAD - retrieve all resources or a resource if a key is specified in a collection (header only),
- POST - saves new instance of resource on the server,
- PUT - replaces existing instance of resource with data provided in request,
- PATCH - updates fields of existing instance of resource with values provided in request,
- DELETE - removes existing instance of resource from the server.
I use
httpie
(https://github.com/jakubroztocil/httpie) as a command line tool to test the servers. You can use any, but all the examples were created with syntax ofhttpie
. Anyway it's recognizable.
Get collection (select) - retrieve all resources in a collection
http GET <serverURL>/api/<collection>
Get resource (selectOne) - retrieve a single resource with specified
http GET <serverURL>/api/<collection>/<id>
restifizer
allows to get the list of resources according provided criteria. If no criteria provided, it return the
first page of maxPageSize
with all available fields.
Example:
http GET localhost:3000/api/users?filter={"username": "test"}&fields=username,createdAt&orderBy={"username": -1}
The set of supported parameters is determined by the data source.
It's json value containing any valid MongoDB query. See docs for details.
Example:
http GET localhost:3000/api/users?filter={"gender": "M", "age": { "$gt": 18 }}
You can use regex values in filter. As we pass JSON in this param it's not possible to use regular expression objects (/pattern/).
You should replace it with $regex
operator. See docs for details.
Comma separated list of field names.
Example:
http GET localhost:3000/api/users?fields=gender,age
It's json value built according MongoDB rules. Use 1 for an ascending sorting, and -1 for a descending sorting. See docs for details.
Example:
http GET localhost:3000/api/users?orderBy={"username": 1, "age": -1}
The maximum number of records in the response. defaultPerPage
is used by default, and maximum is limited with maxPerPage
. See docs for details.
Example:
http GET localhost:3000/api/users?per_page=10
A number of page to return, 1
if a value is not provided. We skip (page - 1) * per_page
records in the query to achieve that. See docs for details.
Parameter for q-searches. It can be any string value.
restifizer
will use it to build the following condition for every value in your qFields
:
{$regex: '.*' + q + ".*", $options: 'i'}
Example:
http GET localhost:3000/api/users?q=John
See q-search
section for details.
Comma separated list of field names.
In order to filter records to fetch from the server you can specify filter
param in URL of your request. It's json
value containing any valid sequelize.js query. Besides simple
filtering by values it supports additional operators, which allow to build more complex query to the server. See
docs for more details.
Example:
http GET localhost:3000/api/users?filter={"gender": "M", "age": { "$gt": 18}, "name": {"$like": "Ro%"}}
Every fetching request returns only one page of data. You can change default page size providing perPage
param in URL.
Default value of the page is 25, and the maximum page size limited by 100.
The page
param allows you to specify page number to fetch. The page numeration begins from 1.
For ordering the records you should use orderBy
param in URL. It should be a valid JSON, containing field names as
keys, and 1 or -1 as values depending on a way to sort (ASC or DESC).
http POST localhost:3000/api/users username=test password=pass
It allows to add new resources to the server. You can provide field values in body with json, or as form params.
http PUT localhost:3000/api/users/<id> username=test password=pass
It completely replaces resource with provided id
with new one specifided in the request. You can provide field values in body with json, or as form params.
Be careful if no value for a field provided, it will be set to undefined.
http PATCH localhost:3000/api/users/<id> password=pass
It partially update resource with provided id
with data from the request. You can provide field values in body with json, or as form params.
If a resource returns an array of associated entity, it’s possible to perform special kind of PATCH
request with special array methods.
The server supports the following methods:
$push
- add new associated items,
$pull
- remove existing associated items.
The body of the method can be different among the resources. Please, refer to resource documentation: mongo.
Example: Add tag to the message tags
http PATCH <serverURL>/api/messages/<messageId> Authorization:'Bearer <access_token>'
{
$push: {
tags: {
tagId: 15
}
}
}
http DELETE 'localhost:3000/api/users/<id>'
It removes the record by id
.
As an extension to standard rest-kit.
http GET localhost:3000/api/users/count?filter={age: { $gt: 18 } }}
It allows to get count of records of specified resource. See docs for details.
It's json value containing any valid MongoDB query. See docs for details.
Example:
{"gender": "M", age: { $gt: 18 } }}
- 200 OK - after any successful request except POST.
- 201 Created - after successful POST.
- 404 Not Found - related resource has not been found for GET (by id), PUT, PATCH, DELETE.
- 400 Bad Request - request params cannot be parsed, or validation failed, or unique checking failed.
- 409 Conflict - mongoose VersionError happened.
It relates to getting the list of resources. Every such response is limited with paging rules:
- an user specifies
per_page
andpage
params in URL, - if params are not provided default rules are applied (see
restifizerOptions.defaultPerPage
), - if
per_page
greater thenrestifizerOptions.maxPerPage
, value ofmaxPerPage
is used.
Controllers are the way to provide needed configuration for your resource and to customize its behaviour.
This param is a way to specify, what data source will be used. Today available restifizer-mongoose-ds and restifizer-sequelize-ds modules. Any resource is bound to mongoose/sequelize model, and this param is a way to specify, what the model your resource uses.
TODO: Put details about transport
Example (mongoose):
const Restifizer = require('restifizer');
const User = require('MongooseUserModel');
class UserController extends Restifizer.Controller {
constructor(options) {
let publicFields = ['firstName', 'lastName', 'thumbnailUrl'];
options = options || {};
Object.assign(options, {
dataSource: {
type: 'mongoose',
options: {
model: User
}
},
path: '/api/users'
});
super(options);
...
});
Example (sequelize):
const Restifizer = require('restifizer');
const User = require('SequelizeUserModel');
class UserController extends Restifizer.Controller {
constructor(options) {
let publicFields = ['firstName', 'lastName', 'thumbnailUrl'];
options = options || {};
Object.assign(options, {
dataSource: {
type: 'sequelize',
options: {
model: User
}
},
path: '/api/users'
});
super(options);
...
});
With this option you specify the paths, where resource will be available at. There are 2 important points:
- it can be a string, or an array if resource is available at several paths,
- you can provide params in paths, and they will be automatically bound to params of requests.
For instance, you can write:
path: ['/api/appData', '/api/users/:owner/appData']
and in the case if an user requests data at /api/users/543d2605e21f85d73b060979/appData
, appData will be filtered by provided value of owner
.
By default all the fields you defined in your model schema (without fields with name starting from "__") are available in your resource. Providing this params are you able exclude some fields from the resource, or add new calculated fields.
If true
, restricts returned and saved fields with fields
array. Default value is true
.
Name of id field. By default: '_id'. This value is used in route params for selectOne, replace, update, delete.
With this param you can specify default filter
value for your controller.
With this param you can specify default orderBy
value for your controller.
For example:
orderBy: {date: -1}
Q-search allows to search data without specifying exact fields of search. Just specify in your controller searchable fields:
qFields: ["login", "firstName", "lastName"]
and set q
param of your GET request (see q
for details).
Available only for restifizer-mongoose-ds data source.
Supported methods with arrays. Default value: ['$addToSet', '$pop', '$push', '$pull']. It's relevant to update (PATCH) only. You can specify such methods in order to manipulate array fields of your resource.
The params and implementations of these methods relate to the same methods in MongoDB:
You are able to customize the behaviour of your controllers very much. And we did all our best to make this process as simple as it's posible.
That's why you're able to specify option in one of the methods, and restifizer
will apply inheritence rules to the of other methods.
There are following rules:
default ->
select ->
selectOne
insert ->
update ->
replace
delete
count
TODO: Change example
For example if you want to define pre
processor for insert, update, partialUpdate, delete that's enough to define it
in your insert
:
var YourController = Restifizer.Controller.extend({
...
insert: {
pre: function (req, res, next) {
...
}
}
All handlers receive scope
in params, where you can get req
for example. In return you can use a promise.
pre(scope)
pre
is a preprocessor that is executed at the beginning before any other logic runs. It's a good point to check
preconditions of your request. For example check if the request is executed by Admin:
pre: function (scope) {
// not admin
if (!this.isAdmin(scope.req)) {
throw HTTP_STATUSES.FORBIDDEN.createError();
}
}
Additionally you can specify pre
-handler on action level, and it will be ran instead.
collectionPost(collection, scope)
When select
returns any collection from db, this collection is passed through this postprocessor.
It's a good point if you want to manipulate with the set of items of the collection, but not with items by themselves.
Additionally you can specify collectionPost
-handler on action level, and it will be ran instead.
post(resource, scope)
post
is postprocessor. It runs immediately before a resource is sent to an user.
At this point you can change the resource by itself. For instance, you can fill calculated fields here:
post: function (resource, scope) {
if (resource.icon) {
resource.icon_url =
scope.req.protocol + "://" + scope.req.get('host') + '/api/resource/' + resource._id + '/icon';
}
return resource;
}
Additionally you can specify post
-handler on action level, and it will be ran instead.
queryPipe: function (query, scope)
You can use queryPipe
if you need to call additional methods at data source query
(docs).
This method is called after all restifizer
calls are done, but immediately before exec
.
TODO Update the section Draw your attention, this method is called in 2 different semantics:
- in selects - in this case we expect you call
callback
, - in IUD-methods - in this case we expect you directly return
query
. In order to make your method workable in both semantics use the way from example bellow (return and callcallback
at the same line).
So, in this example we put populate
to our query pipe:
queryPipe: function (query, req, res, callback) {
return query.populate("fieldToPopulate", callback);
}
prepareData: function (scope)
Prepares data to create new document.
It's a point you can specify defaults for your resource when restifizer
creates it. Returning promise should contain
data
- object containing default values of resource fields.
If you do not specify the handler, restifizer
uses {}
to init the object.
beforeAssignFields: function (scope)
If it's defined, it runs immediately before assignFields
, and has the same params.
You can use it to define what the params will be set.
beforeSave: function (scope)
Handler, called when you create new resource or change existing instance of your resource after all assignments are already done, but immediately before saving it to your database
afterSave: function (scope)
If it's defined, it runs immediately after saveDocument
, and has the same params.
afterChange: function (scope)
Very similar to afterSave
, it calls immediately after it in inserts and updates, but it runs after deletes as well.
It can be a good point to integrate your kind of triggerEngine
. For instance, you can define something like that in
a base controller class of your application:
afterChange: function (scope) {
redisClient.publish(this.ModelClass.modelName + '.' + scope.model.id, scope.action);
}
After that you will be able to subscribe to those events and handle them in flexible and scalable way.
beforeDelete: function (scope)
If it's defined, it runs immediately before removing the document.
beforeArrayMethod(queryParam, methodName, fieldName, scope)
If it's defined, it runs immediately before proceeding of array methods.
It's used to create context, if it's defined. Otherwise, {}
used.
Here you can specify a way, how exceptions will be converted to response.
assignFields(scope)
Assigns all fields from scope.source
to scope.model
.
You can fetch any additional data at this point, or completely change the way fields are assigned.
Default implementation iterates through all the fields, passing them through assignFilter
, and calling assignField
.
assignField(fieldName, scope)
Assigns single field with name fieldName from scope.source
to scope.model
.
At this point you have enough data to deny assigning of exact values to exact fields for instance:
class YourController extends Restifizer.Controller {
assignField(fieldName, scope) {
if (fieldName == 'status' && scope.source[fieldName] == STATUSES.RESTRICTED_STATUS) {
throw HTTP_STATUSES.FORBIDDEN.createError('It\'s not allowable to set restricted status');
}
return super.assignField(fieldName, scope);
},
}
assignFilter(queryParams, fieldName, scope)
Filters assigning field with name fieldName
.
In this method you can synchronously return true
/ false
, allowing / denying to assign exact fields.
For example you can silently skip changing of exact fields for non-admin users:
class YourController extends Restifizer.Controller {
assignFilter(queryParams, fieldName, scope) {
if (fieldName === 'adminOnlyField') {
return this.isAdmin(req);
}
return super.assignField(fieldName, scope);
},
}
createDocument: function (scope)
Create new document instance, called when you create new instance of your resource after all assignments are already done, but immediately before saving it to your database.
saveDocument: function (scope)
Saves document to db, called in inserts and updates.
restifizerOptions
allows you to define common options for all the controllers of your app.
This value is used in all select
requests if no per_page
/perPage
has been provided. Default value is 25
.
This value restricts maximum value of per_page
/perPage
supported with your app. Default value is 100
.
The module uses e2e tests. In order to run them you should start the testing server:
npm run express
Then run:
$ npm test
The test suite will be run and you'll see the results in console.