26

I have a REST API with GETs operations which receive a (long) list of parameters (8 parameters, for example). The aim of this operation is to search and filter elements.

Which is the best practice to manage this scenario?:

(1) Receive a list of parameters?:

public ResultType Get(int p1, int p2, string p3...) { ... }

(2) Or receive an object that encapsulate these parameters?:

public class MyClass
{
    public int X { get; set; }
    public int Y { get; set; }
    public string Z { get; set; }
}

public ResultType Get(MyClass parameter) { ... }

Think in an eCommerce scenario in which you want to search and filter "products" by name, description, category, brand, model, price, etc...

Jose Alonso Monge
  • 369
  • 1
  • 3
  • 5
  • 1
    Position is lesser of an identifier than a proper name for a value. – S.D. Aug 31 '18 at 12:27
  • Why do you have 8 parameters? What do they do? Is there some decision logic involved (and is it a mess)? Can you split that api/method into several methods that take 3 or 4 parameters? – Filip Milovanović Aug 31 '18 at 13:17
  • 2
    8 parameters is just an example. My Get operation is a "search" method. Think in an eCommerce scenario in which you want to search and filter "products" by name, description, category, brand, model, price, etc... I've edited the question to add context – Jose Alonso Monge Sep 02 '18 at 11:45

3 Answers3

16

I have a REST API with GETs operations which receive a (long) list of > parameters. Which is the best practice to manage this scenario?

AFAIK, there is no firmly established best practice (sorry). However, one can make some recommendations:

  • Try to avoid using POST (instead of GET) if the request is safe (i.e. side-effect free, in particular not modifying data). If the parameters are very large, you may have to use POST to get around length limitations, but usually this is not a problem (most software supports quite long URLs), and safe requests should use GET to allow optimizations such as caching and prefetching.

  • If you use GET, you must send your parameters as URL parameters1). In that situation, the natural and common solution is to use a list of parameters, so that's what I'd recommend. Yes, the list will be long, but the REST API is (probably) intended for programmatic use, so I don't see a problem with this. Users of the API are free to encapsulate the parameters in an object inside their own code.

  • Technically, you could also put an object into an URL parameter (as JSON, XML or whatever), but that is unusual, so I would avoid it if possible.

1) Strictly speaking, you can use a body with a GET request, but this is unusual and generally not recommended; see e.g. HTTP GET with request body.


Finally, just as with methods in source code that have long parameter lists, you might want to consider whether the REST API needs a refactoring. Just like in source code, such a long parameter list indicates the API endpoint is doing many different things. Does it make maybe make sense to split it up, or provide default settings? But that's a different question...

sleske
  • 10,095
  • 3
  • 29
  • 44
  • Technically you can also send a body with a GET although unusual – Ewan Aug 31 '18 at 11:08
  • also, are you sure you mean idempotent, I would say 'cacheable' GetTodaysDayName() might be considered idempotent, but you wouldn't want to cache it – Ewan Aug 31 '18 at 11:12
  • Let the GET vs POST battle commence!!!! :) – Ewan Aug 31 '18 at 11:43
  • @Ewan: As to caching GetTodaysDayName: Assuming you need today's name ten times a second, and don't care to use the wrong day for a few seconds, you do most likely want to cache it. Still, I wrongly used "idempotent", what I had in mind was "safe" (=not modifying). Edited. – sleske Aug 31 '18 at 11:49
  • 1
    @Ewan constructive battles in which the "opponents" enrich the response of each other are more than welcome. Thanks to both of you for a very comprehensive overview on all the options ! – Christophe Aug 31 '18 at 11:50
  • By the way, this shows that the question of the OP was very relevant and deserves an upvote ;-) – Christophe Aug 31 '18 at 11:52
7

Best practice is to POST the parameters as an object.

This avoids the URL length limit and other problems with query strings. If you send multiple parameters in JSON then an object is the standard way of doing it, so deserialising to one makes sense.

You can avoid creating random 'Request' objects that are only used in your Controllers by deserialising to a dynamic object if you like; although casting to the right types afterwards can be equally messy.

In the old days you would have been able to have multiple parameters in your controller action automatically bound to an incoming JSON object's fields. But this no longer works out of the box in .net core.

Although there is this, which I might be tempted to try

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/application-model?view=aspnetcore-2.1#application-model-usage-in-webapicompatshim

Edit: Just going to add a few points on the use of GET

  • Caching: GET will be cached by clients that obey the HTTP spec. But the spec is designed to make webpages load faster. The caching is helpful if your API result has the same cache requirements as a webpage, but unhelpful if it doesn't. You can add your own caching of POST responses in the client if required.

  • URL length: This is mainly an issue when you are sending an array. Obviously an array can easily get very long and the query string parameter names will be repeated for each item. However, even if you are only sending one string, technically that string can be very very long.

  • Logging: By default many web servers will log the entire query string. Often you don't want parameter data ending up in plain text logs.

  • GET with body: This would seem to be the perfect answer, satisfying REST purists while allowing a nice data structure, but it is unusual and frowned upon, a POST is the standard way to send a body.

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • 6
    I don't think this is agreed-upoin best practice. In particular, if the request is idempotent, it _is_ best practice to use `GET` and not `PUT` (for cacheability, among other things). And URL length limits are quite large nowadays, so not necessarily a problem. – sleske Aug 31 '18 at 09:52
  • @sleske If you think GET is better you should write an answer about why. String z can be 4Mb long though – Ewan Aug 31 '18 at 09:58
  • actually just googling and the max json string length seems uncertain – Ewan Aug 31 '18 at 10:02
  • Given the context, there is no need to have the request parameters *not* in your logs - unless you do not want to do _analytics_ on what people sought after on your page ;) `GET` makes totally sense from two perspectives: a) analytics and b) a user could store the query for later or share the query. – Thomas Junk Sep 02 '18 at 14:54
  • 1
    @ThomasJunk what context do you refer to? the string is called 'z', there could be literally anything in it. whats the GDPR fine $20m? – Ewan Sep 02 '18 at 14:59
  • »Think in an eCommerce scenario in which you want to search and filter "products" by name, description, category, brand, model, price, etc...« IANAL, but I think as long as you do analytics on the query parameters and their frequency, this should be GDPR compliant ;) Not to speak of getting _consent_ :P – Thomas Junk Sep 02 '18 at 15:01
  • @ThomasJunk let's hope i'm not buying Kosher food or something else that would identify my religion – Ewan Sep 02 '18 at 15:08
  • As long as I do not correlate this information ("this search") to your personal data ("a user with a certain IP"), it is from my understanding totally legal. But we digress ;) – Thomas Junk Sep 02 '18 at 15:13
  • 1
    @ThomasJunk the standard log will also have the ip. But yes we digress. the point isnt that you log the info, its that you dont realise you are logging the info and get caught out in an audit. Best practice is to only log specfic info, not anything the user sends – Ewan Sep 02 '18 at 15:15
2

My approach:

  1. Implement a custom URL parameters builder class and encode (base 64) the entire query parameters with StringBuilder + delimiter char to differentiate each parameter.

  2. The receiving side should use the same builder to decode and construct the query parameter object.

  3. The get URL looks like this: http(s)://www.mysite.com/paramsId, where paramsId is a base64 encoded parameter list object.

    @Data
    @Builder(builderClassName = "Builder")
    @NoArgsConstructor
    @AllArgsConstructor
    @EqualsAndHashCode(callSuper = false)
    class OrderParamsBuilder {       
        private String price;
        private String quantity;
        private int itemsCount;
        // add more parameters here
    }
    

Another easy way is to go for a POST request, which I do not recommend.

Glorfindel
  • 3,137
  • 6
  • 25
  • 33