Skip to content

Commit

Permalink
Merge pull request #133 from yahoo/formatter
Browse files Browse the repository at this point in the history
Add responseFormatter support
  • Loading branch information
redonkulus committed Dec 3, 2015
2 parents 0342e28 + 80e8bf3 commit d3b1666
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 12 deletions.
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Fetchr
# Fetchr

[![npm version](https://badge.fury.io/js/fetchr.svg)](http://badge.fury.io/js/fetchr)
[![Build Status](https://travis-ci.org/yahoo/fetchr.svg?branch=master)](https://travis-ci.org/yahoo/fetchr)
[![Dependency Status](https://david-dm.org/yahoo/fetchr.svg)](https://david-dm.org/yahoo/fetchr)
[![devDependency Status](https://david-dm.org/yahoo/fetchr/dev-status.svg)](https://david-dm.org/yahoo/fetchr#info=devDependencies)
[![Coverage Status](https://coveralls.io/repos/yahoo/fetchr/badge.png?branch=master)](https://coveralls.io/r/yahoo/fetchr?branch=master)
[![Coverage Status](https://coveralls.io/repos/yahoo/fetchr/badge.png?branch=master)](https://coveralls.io/r/yahoo/fetchr?branch=master)

Universal data access layer for web applications.

Typically on the server, you call your API or database directly to fetch some data. However, on the client, you cannot always call your services in the same way (i.e, cross domain policies). Instead, XHR requests need to be made to the server which get forwarded to your service.

Having to write code differently for both environments is duplicative and error prone. Fetchr provides an abstraction layer over your data service calls so that you can fetch data using the same API on the server and client side.
Having to write code differently for both environments is duplicative and error prone. Fetchr provides an abstraction layer over your data service calls so that you can fetch data using the same API on the server and client side.

## Install

Expand Down Expand Up @@ -133,7 +133,7 @@ fetcher
.end(function (err, data, meta) {
// handle err and/or data returned from data fetcher in this callback
});

// for create you can use the body() method to pass data
fetcher
.create('data_api_create')
Expand Down Expand Up @@ -258,6 +258,29 @@ fetcher
});
```
## XHR Response Formatting
For some applications, there may be a situation where you need to modify an XHR response before it is passed to the client. Typically, you would apply your modifications in the service itself. However, if you want to modify the XHR responses across many services (i.e. add debug information), then you can use the `responseFormatter` option.
`responseFormatter` is a function that is passed into the `Fetcher.middleware` method. It is passed three arguments, the request object, response object and the service response object (i.e. the data returned from your service). The `responseFormatter` function can then modify the service response to add additional information.
Take a look at the example below:
```js
/**
Using the app.js from above, you can modify the Fetcher.middleware
method to pass in the responseFormatter function.
*/
app.use('/myCustomAPIEndpoint', Fetcher.middleware({
responseFormatter: function (req, res, data) {
data.debug = 'some debug information';
return data;
}
}));
```
Now when an XHR request is performed, your response will contain the `debug` property added above.
## CORS Support
Fetchr provides [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) support by allowing you to pass the full origin host into `corsPath` option.
Expand Down
24 changes: 16 additions & 8 deletions libs/fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,20 @@ Fetcher.isRegistered = function (name) {
* Returns express/connect middleware for Fetcher
* @method middleware
* @memberof Fetcher
* @param {Object} [options] Optional configurations
* @param {Function} [options.responseFormatter=no op function] Function to modify the response
before sending to client. First argument is the HTTP request object,
second argument is the HTTP response object and the third argument is the service data object.
* @returns {Function} middleware
* @param {Object} req
* @param {Object} res
* @param {Object} next
*/
Fetcher.middleware = function () {
Fetcher.middleware = function (options) {
options = options || {};
var responseFormatter = options.responseFormatter || function noOp(req, res, data) {
return data;
};
return function (req, res, next) {
var request;
var error;
Expand Down Expand Up @@ -314,14 +322,14 @@ Fetcher.middleware = function () {
}
if (err) {
var errResponse = getErrorResponse(err);
res.status(errResponse.statusCode).json(errResponse.output);
res.status(errResponse.statusCode).json(responseFormatter(req, res, errResponse.output));
return;
}
if (req.query.returnMeta) {
res.status(meta.statusCode || 200).json({
res.status(meta.statusCode || 200).json(responseFormatter(req, res, {
data: data,
meta: meta
});
}));
} else {
// TODO: Remove `returnMeta` feature flag after next release
res.status(meta.statusCode || 200).json(data);
Expand Down Expand Up @@ -361,16 +369,16 @@ Fetcher.middleware = function () {
if (meta.headers) {
res.set(meta.headers);
}
if(err) {
if (err) {
var errResponse = getErrorResponse(err);
res.status(errResponse.statusCode).json(errResponse.output);
res.status(errResponse.statusCode).json(responseFormatter(req, res, errResponse.output));
return;
}
var responseObj = {};
responseObj[DEFAULT_GUID] = {
responseObj[DEFAULT_GUID] = responseFormatter(req, res, {
data: data,
meta: meta
};
});
res.status(meta.statusCode || 200).json(responseObj);
});
}
Expand Down
110 changes: 110 additions & 0 deletions tests/unit/libs/fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,116 @@ describe('Server Fetcher', function () {
});

});

describe('Response Formatter', function () {
describe('GET', function () {
it('should modify the response object', function (done) {
var operation = 'read';
var statusCodeSet = false;
var params = {
uuids: ['cd7240d6-aeed-3fed-b63c-d7e99e21ca17', 'cd7240d6-aeed-3fed-b63c-d7e99e21ca17'],
id: 'asdf'
};
var req = {
method: 'GET',
path: '/' + mockService.name + ';' + qs.stringify(params, ';'),
query: {
returnMeta: true
}
};
var res = {
json: function(response) {
expect(response).to.exist;
expect(response).to.not.be.empty;
expect(response).to.contain.keys('data', 'meta', 'modified');
expect(response.data).to.contain.keys('operation', 'args');
expect(response.data.operation.name).to.equal(operation);
expect(response.data.operation.success).to.be.true;
expect(response.data.args).to.contain.keys('params');
expect(response.data.args.params).to.deep.equal(params);
expect(response.meta).to.be.empty;
expect(statusCodeSet).to.be.true;
done();
},
status: function(code) {
expect(code).to.equal(200);
statusCodeSet = true;
return this;
},
send: function (code) {
console.log('Not Expected: middleware responded with', code);
}
};
var next = function () {
console.log('Not Expected: middleware skipped request');
};
var middleware = Fetcher.middleware({responseFormatter: function (req, res, data) {
data.modified = true;
return data;
}});

middleware(req, res, next);
});
});
describe('POST', function () {
it('should modify the response object', function (done) {
var operation = 'create',
statusCodeSet = false,
req = {
method: 'POST',
path: '/' + mockService.name,
body: {
requests: {
g0: {
resource: mockService.name,
operation: operation,
params: {
uuids: ['cd7240d6-aeed-3fed-b63c-d7e99e21ca17', 'cd7240d6-aeed-3fed-b63c-d7e99e21ca17'],
id: 'asdf'
}
}
},
context: {
site: '',
device: ''
}
}
},
res = {
json: function(response) {
expect(response).to.exist;
expect(response).to.not.be.empty;
expect(response.g0).to.contain.keys('data', 'meta', 'modified');
var data = response.g0.data;
expect(data).to.contain.keys('operation', 'args');
expect(data.operation.name).to.equal(operation);
expect(data.operation.success).to.be.true;
expect(data.args).to.contain.keys('params');
expect(data.args.params).to.equal(req.body.requests.g0.params);
expect(statusCodeSet).to.be.true;
done();
},
status: function(code) {
expect(code).to.equal(200);
statusCodeSet = true;
return this;
},
send: function (code) {
console.log('Not Expected: middleware responded with', code);
}
},
next = function () {
console.log('Not Expected: middleware skipped request');
},
middleware = Fetcher.middleware({responseFormatter: function (req, res, data) {
data.modified = true;
return data;
}});

middleware(req, res, next);
});
});
});
});

describe('CRUD Interface', function () {
Expand Down

0 comments on commit d3b1666

Please sign in to comment.