Feathers: REST Client : Accept data or body params for heavy GET requests

Created on 27 May 2019  路  3Comments  路  Source: feathersjs/feathers

Hi,

I've had some issues with long URLs in GET requests. I need to pass a long list of params in some of my "find" queries.

Current solution is to create a new middleware and method overriding.

// allow x-http-method-override header for GET methods
exports.default = function (req, res, next) {
    if (req.headers['x-http-method-override'] === 'GET' && req.method === 'POST') {
        req.method = 'GET';
        req.query = req.body;
        delete req.body;
    }
    next();
};

Then, I consume my service like this:

await awesomeService.create(
          {
             someParam: aVeryLongArray // will replace req.query server side thanks to the middleware, and thus bypass URL limitations of browsers
          },
          {
            headers: {
              "x-http-method-override": "GET" // tell the server that this is actually a GET request, so it triggers our middleware
            }
          }
        );

Thanks a lot to Sebastian for the help on Slack (can't find your GitHub handle sorry).

According to this SO answer this is a valid pattern because POST HTTP word is not limited to creation per se, but also associated with any other kind of action that can't be classified. So an "heavily parametered find" could be considered as a relevant use case for POST.

But... the problem is that in Feathers POST request are strongly tied to a "creation" action. When using the REST client, that means we must use the create method to find data, which is disturbing, and server side, that means setting the relevant permission on the create hook, which is also disturbing.

I propose to support body or data field in GET request (so get and find methods). I think the implementation is trivial (and I'd be glad to PR), however I wonder:

  • if it is a good idea (mostly regarding to browsers, can they scrap out data or body field in get request?)
  • what would be the best pattern (a body param, a data param?)

Most helpful comment

I've been thinking of making the REST client a little more flexible so you can more easily instantiate your own customized client services. Allowing to POST queries with JSON would also avoid all the URL string conversion and array parsing limits. At the moment customizing a REST service on the client looks like this (this is also how you can use different endpoints):

const FetchService = require('@feathersjs/rest-client/lib/fetch');

class MyFetchService extends FetchService {
  get (id, params = {}) {
    if (typeof id === 'undefined') {
      return Promise.reject(new Error(`id for 'get' can not be undefined`));
    }

    return this.request({
      url: this.makeUrl({}, id),
      method: 'POST',
      data: params.query,
      headers: Object.assign({
        'x-http-method-override': 'GET'
      }, params.headers)
    }, params).catch(toError);
  }
}

app.use('/someservice', new MyFetchService({
  connection: window.fetch,
  name: 'someservice',
  base: 'mybaseUrl',
  options: {}
}));

A non-breaking change could make it possible to just do

const feathers = require('@feathersjs/feathers');
const rest = require('@feathersjs/rest-client');

const app = feathers();

// Connect to the same as the browser URL (only in the browser)
app.configure(rest({
  base: '/',
  Service: MyFetchService
}));

All 3 comments

I've been thinking of making the REST client a little more flexible so you can more easily instantiate your own customized client services. Allowing to POST queries with JSON would also avoid all the URL string conversion and array parsing limits. At the moment customizing a REST service on the client looks like this (this is also how you can use different endpoints):

const FetchService = require('@feathersjs/rest-client/lib/fetch');

class MyFetchService extends FetchService {
  get (id, params = {}) {
    if (typeof id === 'undefined') {
      return Promise.reject(new Error(`id for 'get' can not be undefined`));
    }

    return this.request({
      url: this.makeUrl({}, id),
      method: 'POST',
      data: params.query,
      headers: Object.assign({
        'x-http-method-override': 'GET'
      }, params.headers)
    }, params).catch(toError);
  }
}

app.use('/someservice', new MyFetchService({
  connection: window.fetch,
  name: 'someservice',
  base: 'mybaseUrl',
  options: {}
}));

A non-breaking change could make it possible to just do

const feathers = require('@feathersjs/feathers');
const rest = require('@feathersjs/rest-client');

const app = feathers();

// Connect to the same as the browser URL (only in the browser)
app.configure(rest({
  base: '/',
  Service: MyFetchService
}));

Hi, I also have the same issue, where find request should be able to take in multiple params, and we would hit the URL limit. So I've been struggling to see if I can do a POST and route to "find" instead of "create".

I tried what @eric-burel suggested, overriding the HTTP request method type from POST to GET, but it fails trying to find the "create" method in the service instead of routing to "find". I verified that the request object method is indeed getting changed to "GET", but maybe feathers routing happens before this change?

app.use('/appointments', function(req, res, next) {
if (req.method === 'POST') { // Doing this only for this service.
req.method = 'GET';
req.query = req.body;
delete req.body;
}
next();
}, {
find: function(params, callback) {
// do something
}
});

Could either of you please clarify if I'm missing something here? Or even better, let me know if there's a better way to do this?

This can be done in with the new custom methods (https://github.com/feathersjs/feathers/issues/1976) coming v5

Was this page helpful?
0 / 5 - 0 ratings

Related issues

corymsmith picture corymsmith  路  4Comments

intumwa picture intumwa  路  3Comments

rrubio picture rrubio  路  4Comments

codeus-de picture codeus-de  路  4Comments

arve0 picture arve0  路  4Comments