loopback-connector-flexirest
LoopBack FLEXIREST connector allows loopback application to interact with backend REST APIs. It leverages loopback-connector-rest for basic building blocks and adds additional "flexible" features to allow interaction with backend services.
Installation
npm install loopback-connector-flexirest
Features
- Use custom folder (endpoints) to organize operations
- Allow outgoing request parameters transformation
- Allow incoming response body transformation
- Allow use of templating library like handlebars for transformation
- Add new hooks before / after outgoing and incoming transformation
- Allow custom error handler for functions
- Extend operation template schema to allow errorHandler, incomingTransform and outgoingTransform to be added as configuration
- Add a "shared" flag in operation template to disable the operation to be added as a remote method
Usage
Configuration in datasources.json
You can configure the Flexirest connector by editing datasources.json. The baseURL attribute gets prefixed to all the URLs defined in operation templates. All fields are required.
"myNewDataSource": {
"name": "myNewDataSource",
"connector": "flexirest",
"path": "endpoints",
"baseURL" : "http://XXX.YYY.ZZZ/api"
}
Operation templates
Create a new folder under application root as "endpoints" and store all your operation JSON files. Each operation file should have one template defined. For understanding the template structure, follow the documentation provided by Strongloop. Also, look for example-templates in test folder.
Error Handlers
Custom error handlers can be defined for each functions to allow better non-functional as well functional error handling for backend responses
Outgoing Transformation
Outgoing transformation provides ability to transform the parameters before they are applied to the operation functions.
Incoming Transformation
Incoming transformation provides ability to transform the response body received from the backend system
Syntax to configure error handler & transformations
There are two ways in which the error handler, outgoing and incoming transformation functions can be added:
- Adding script to boot folder - Functions are added after model association
- Adding configuration to operation JSON templates - Functions are added to datasource
Adding Script to boot folder
Add boot script as follows:
module.exports = function(app) {
var customFunction = app.models.backend.customFunction;
var errorHandler = function(err,body,response,callback) {
if(err)
return callback(err);
if(body.error && body.error.errorCode) {
var error = new Error(body.error.message);
error.statusCode = 500;
error.code = 'SYS' + body.error.errorCode;
return callback(error);
}
return callback(null,body,response);
}
var outgoingTransformFn = function(parameters) {
parameters.id = 'PRE' + parameters.id;
return parameters;
};
var incomingTransformFn = function(body) {
return body.query.results;
};
customFunction.errorHandler = errorHandler;
customFunction.outgoingTransform = outgoingTransformFn;
customFunction.incomingTransform = incomingTransformFn;
};
Adding configuration to operation JSON templates
First modify operation template with additional attributes: (errorHandler, outgoingTransform, incomingTransform)
{
"template": {
"method": "GET",
"url": "/api/backend/{id}",
"query": {
"id": "{id}"
}
},
"functions": {
"customFunction": ["id"]
},
"shared" : false,
"errorHandler": "customFunction.errorHandler",
"outgoingTransform": "customFunction.outgoingTransform",
"incomingTransform": "customFunction.incomingTransform"
}
Secondly, add JS file at the same location as of the operation template, with following code. The connector parses the handler string and splits by '.' (dot). First segment is considered as the filename to load and the second segment is considered as the function name.
exports.errorHandler = function(err,body,response,callback){
console.log('customFunction errorHandler called');
if(!err) {
if(!body.query.results) {
err = new Error('No results found');
err.statusCode = 404;
}
}
callback(err,body,response);
};
exports.outgoingTransform = function(parameters){
console.log('customFunction outgoingTransform called');
return parameters;
};
exports.incomingTransform = function(body){
console.log('customFunction incomingTransform called');
return body;
};
Use of external transformation library
To use external library like handlebars:
- Modify datasources.json as:
Add transformer attribute to datasource definition.
"myNewDataSource": {
"name": "myNewDataSource",
"connector": "flexirest",
"endpoints": "endpoints",
"baseURL" : "http://XXX.YYY.ZZZ/api"
"transformer" : {
"library" : "handlebars",
"ext" : "hbs"
}
}
- Modify operation template
In place of mentioning the function name for outgoing and incoming transformation, specify the name of the transformation template. Check for examples at example-templates.
{
"template": {
"method": "GET",
"url": "/api/backend/{id}",
"query": {
"id": "{id}"
}
},
"functions": {
"customFunction": ["id"]
},
"shared" : false,
"errorHandler": "customFunction.errorHandler",
"outgoingTransform": "outgoing.hbs",
"incomingTransform": "incoming.hbs"
}
Transformation Hooks
Transformation hooks enable application to intercept the parameters during outgoing transformation and response during incoming transformation. All hooks are attached to the connector instance. The hooks leverages observer mixin methods that are available as part of the connector.
before/after outgoingTransform
Context in case of outgoingTransform contains:
- name : Function name
- parameters : An Object carrying all function parameters
connector.observe('before outgoingTransform', function(ctx, next) {
console.log('before outgoingTransform');
next();
});
connector.observe('after outgoingTransform', function(ctx, next) {
console.log('after outgoingTransform');
next();
});
before/after incomingTransform
Context in case of incomingTransform contains:
- name : Function name
- response : Instance of the response object received from request module
- body : JSON object received from the backend service
connector.observe('before incomingTransform', function(ctx, next) {
console.log('before incomingTransform');
next();
});
connector.observe('after incomingTransform', function(ctx, next) {
console.log('after incomingTransform');
next();
});
To debug
Start node with debug prefix as DEBUG=loopback:connector:flexirest node .
Roadmap
- Improve documentation
- Use of mixins to add transformer functions
- Add promises in place of callbacks
- Add cache hooks with full / partial-refresh strategy
- Ability to create a higher level function by orchestrating with multiple low level function
- Test coverage
See Also