512

I'm currently designing and implementing a RESTful API in PHP. However, I have been unsuccessful implementing my initial design.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

So far pretty standard, right?

My problem is with the first one GET /users. I was considering sending parameters in the request body to filter the list. This is because I want to be able to specify complex filters without getting a super long url, like:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Instead I wanted to have something like:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

which is much more readable and gives you great possibilities to set complex filters.

Anyway, file_get_contents('php://input') didn't return the request body for GET requests. I also tried http_get_request_body(), but the shared hosting that I'm using doesn't have pecl_http. Not sure it would have helped anyway.

I found this question and realized that GET probably isn't supposed to have a request body. It was a bit inconclusive, but they advised against it.

So now I'm not sure what to do. How do you design a RESTful search/filtering function?

I suppose I could use POST, but that doesn't seem very RESTful.

Jonas
  • 97,987
  • 90
  • 271
  • 355
Erik B
  • 35,941
  • 21
  • 106
  • 122
  • 7
    possible duplicate of [RESTful URL design for search](http://stackoverflow.com/questions/207477/restful-url-design-for-search) – outis Mar 02 '12 at 11:03
  • 74
    Be careful!!! GET method must be IDEMPOTENT, and must be "cacheable". If you send information in the body How can the system cache your request? HTTP allows caching GET request using only the URL, not the request body. For instance, this two requests: http://example.com { test:"some" } http://example.com { anotherTest:"some2" } are considered the same by the cache system: Both of them have exactly the same URL – jfcorugedo Feb 23 '15 at 11:45
  • 20
    Just to add, you should POST to the /users (collection) and not /user (single user). – Mladen B. Jun 24 '15 at 11:34
  • 1
    Another point to consider is most app servers have access logs that logs the url and so might be anything in between. So there might be some un-intended info leak on GET. – user3206144 Sep 24 '18 at 15:08

7 Answers7

423

The best way to implement a RESTful search is to consider the search itself to be a resource. Then you can use the POST verb because you are creating a search. You do not have to literally create something in a database in order to use a POST.

For example:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

You are creating a search from the user's standpoint. The implementation details of this are irrelevant. Some RESTful APIs may not even need persistence. That is an implementation detail.

jdphenix
  • 13,519
  • 3
  • 37
  • 68
Jason Harrelson
  • 4,983
  • 1
  • 9
  • 2
  • 9
    This should be accepted as the answer by the OP. It is the most correct response to the question. @jason-harrelson is spot-on by saying that persistence is not needed to support the intended use of POST. The nice part about using POST, for a search resource, is the ability to use this as a future feature like saved searches, or suggested searches for a user of your API. – Mike Clymer Nov 08 '13 at 17:35
  • 234
    One significant limitation to using a POST request for a search endpoint is that it cannot be bookmarked. Bookmarking search results (particularly complex queries) can be quite useful. – couchand Jan 17 '14 at 20:57
  • 80
    Using POST to make searches may break the REST cache constraint. http://whatisrest.com/rest_constraints/cache_excerps – Filipe Jan 18 '14 at 12:21
  • 1
    I wonder if it's then possible to have the PRG pattern for a post-redirect-get. – Stephane Apr 25 '14 at 15:51
  • 61
    Searches, by their nature, are transient: data evolves between two searches with the same parameters, so I think that a GET request does not map cleanly to the search pattern. Instead, the search request should be POST (/Resource/search), then you can save that search and redirect to a search result, e.g. /Resource/search/iyn3zrt. That way, GET requests succeed and make sense. – sleblanc Apr 28 '14 at 17:53
  • 40
    I don't think post is suitable method for searching, data for normal GET requests could vary over time too. – wonder May 29 '15 at 09:34
  • 2
    what about how GitHub does it. I like how they spit it out into resources still but I guess if you're doing a Google-like free text search you cant' really split that out into concrete resources in the url. GitHub does it something like this: /search/[resourceName]?q=John... where q is the keyword or whatever the text was typed into the search box but you're searching for a specific resource type in this case – PositiveGuy Jun 22 '15 at 15:39
  • 17
    Might as well just dump all verbs, and use POST for everything... But seriously, if we were to extrapolate this logic, then all dynamic pages should be retrieved using POST instead of GET, because we're creating a new (and potentially unique) HTML / JSON / whatever document with every request. Can't believe this answer has so many upvotes. – Alec Mev Sep 09 '15 at 15:28
  • @OlegsJeremejevs: it's said that GETs should be idempotent (always return same results) - search will obviously return different results at different time. Now, /search isn't a resource at all, I'd say. I would put that under filtering the actual /users (or whatever) endpoint, what do you think? – Zlatko Oct 20 '15 at 08:02
  • 7
    @Zlatko: Think of `GET /users` - you'd need pagination for that, right? Pagination is a type of search / filter too, since you pass some parameters in and get a subset of resources. Does that warrant `POST`? – Alec Mev Oct 20 '15 at 11:14
  • 7
    Exactly what I'm thinking. That's why I also think POST is wrong here. Search should just be a filter on `GET /resource` calls. – Zlatko Oct 20 '15 at 11:17
  • 11
    @OlegsJeremejevs Idempotent doesn't mean "always return same results". Otherwise I could never GET from any resource or collection which also accepts POSTs or PUTs – Ben Aaronson Jan 12 '16 at 13:06
  • 2
    @OlegsJeremejevs "Might as well just dump all verbs, and use POST for everything" I'm not sure what you mean. Here we're posting to a collection (searches) to create a new resource (search) in that collection. Isn't that what it's usually used for? – Ben Aaronson Jan 12 '16 at 13:09
  • 98
    This is absolutely the worst possible answer. I can't believe it has so many upvotes. This answer explains why: http://programmers.stackexchange.com/questions/233164/how-do-searches-fit-into-a-restful-interface – richard Feb 13 '16 at 06:06
  • 2
    After going through the link posted by richard, I don't think POST is the right RESTful way. – Srividya Sharma May 17 '16 at 11:30
  • 2
    I don't think GET with parameters is always the right choice. You might be searching by sensitive data, such as card numbers or SSNs, and it is safer to hide that information in POST body. Hiding everything in post data is a good way to prevent malicious attacks or to stop clever users from doing things they should not. – jkerak Aug 30 '16 at 13:43
  • 2
    @jkerak You shouldn't have sensitive data like ssn's going over the wire at all anyway, especially for a search! Use a surrogate key or something not sensitive. – richard Oct 26 '16 at 23:46
  • 4
    Using GET for search does not violate the idempotent nature of GET. The point of 'idempotent 'in regards to REST is that calling that operation does not itself affect the resource. Calling GET with params 10 times does not change the results. – Ben George Nov 01 '16 at 23:42
  • 4
    Very creative answer: just think of the search as a resource and -poof- there is your justification for using POST instead of GET. Convenient but wrong, in my opinion. – Captain Sensible Dec 05 '16 at 10:10
  • @DiegoDeberdt Everything's a resource if you're brave enough. (sorry) – phil294 Apr 22 '17 at 23:26
  • 4
    Facebook and Netflix said the hell with REST and creayed their own specs for querying. GraphQl and Falcor and tney are OSS. – user432024 Apr 28 '17 at 03:38
  • Search or filtering should never be done using the POST verb. It's totally wrong! – Antonio Jun 08 '17 at 15:10
  • 1
    So a lot of people are suggesting that POST is wrong. I normally use GET and the query string to specify my filters (encoding my filters for more complicated searches) but what if I wanted to check for the existence of a resource, or a user resource to be more specific by username and password, simulating login authentication? This is obviously sensitive data (password) I wouldn't want exposed. Should I post both pieces of data to an enpoint and return a matching resource? Should I create an RPC type call and return true/false or something? – georaldc Jun 27 '17 at 18:39
  • I think ideally you'd have the client site receive the search as a GET so it can be bookmarked, but if there was a dedicated API server behind it, it would be best to POST the query. Knowing that not everyone has this architecture- its just a matter of deciding if you need the search url to be cachable/bookmarked or not. – user Feb 01 '18 at 05:43
  • 1
    Lots of opinions here. Maybe the deciding factor should be the type of data you're searching against. For example, it could be a file or a picture used to for comparing. Or maybe the search text is too difficult to url encode. Those seem like reasons to post the data. – Jesse Seger Apr 25 '18 at 13:26
  • 1
    That's possibly the most tenuous definition of a POST scenario I've ever heard. – ComeIn Jan 31 '19 at 02:55
  • It does not only break bookmarking. It also breaks navigation entirely - try to go back to search results. Awful experience. My rule of thumb is: never ever use POST without redirection to GET in a browser experience. – Robo Robok Mar 01 '19 at 05:30
  • I suppose with SPAs we could build FrontEnd navigation with GET and BackEnd queries with POST. That way bookmarking still works but you must map the GET url to the POST query in FrontEnd. Just a thought seems like there is no consensus on the internet. – ColacX Jun 28 '19 at 14:52
  • 1
    Caching won't be supported as HTTP allows caching GET request using only the URL, not the request body. – Sazzad Hissain Khan Feb 24 '20 at 15:20
  • As others have mentioned, I see 3 problems with POST. You will have problem with (i) people unable to bookmark the search, (ii) the browser's back button leading to unexpected behavior, (iii) caching the result with services like Redis, because that is URL dependent. To me, (iii) was a deal breaker; way too much resource consumption. I do not like ugly URLs either, but ultimately decided that was necessary evil given the current constraints. – Jason May 07 '21 at 13:13
174

If you use the request body in a GET request, you're breaking the REST principle, because your GET request won't be able to be cached, because cache system uses only the URL.

What's worse, your URL can't be bookmarked, because the URL doesn't contain all the information needed to redirect the user to this page.

Use URL or Query parameters instead of request body parameters, e.g.:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

In fact, the HTTP RFC 7231 says that:

A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.

For more information take a look here.

Sergey Brunov
  • 11,755
  • 7
  • 39
  • 71
jfcorugedo
  • 8,104
  • 7
  • 33
  • 45
  • 42
    Learn from my mistake - I designed an api using the accepted answer's suggestion (POSTing json), but am moving over to url parameters. Bookmark-ability may be more important than you think. In my case, there was a need to direct traffic to certain search queries (ad campaign). Also, using the history API makes more sense with URL parameters. – Jake Aug 16 '17 at 13:57
  • 3
    It depends on how its used. If you are linking to a URL that loads the page based on those parameters, it make sense, but if the main page is doing an AJAX call only to get the data based on filter parameters, you can't bookmark that anyway because its an ajax call and has no bearing. Naturally, you could also bookmark a URL that when you go there builds up a filter and POSTs that to the ajax call and it would work just fine. – Daniel Lorenz Sep 20 '18 at 17:33
  • @DanielLorenz For the best user experience, the URL should still be changed through the History API in that case. I can't stand when a website doesn't allow using the browser back functionality to navigate to previous pages. And if it is a standard server side generated page the only way to make it bookmarkable would to use a GET request. It seems that good ol' query parameters are the best solution. – Nathan Jan 17 '19 at 05:31
  • @Nathan I think I misread this answer. I was talking about using query string parameters in a get. You should never use body parameters in a GET call because that would be completely useless. I was talking more about a GET with query string could be used/bookmarked and then at startup of the page, you can use those parameters to build up a filter to POST, using those parameters to get the data. History would still work fine in that scenario. – Daniel Lorenz Jan 17 '19 at 14:29
  • @DanielLorenz Ah okay that makes sense. I think I misunderstood what you were saying. – Nathan Jan 18 '19 at 01:15
89

It seems that resource filtering/searching can be implemented in a RESTful way. The idea is to introduce a new endpoint called /filters/ or /api/filters/.

Using this endpoint filter can be considered as a resource and hence created via POST method. This way - of course - body can be used to carry all the parameters as well as complex search/filter structures can be created.

After creating such filter there are two possibilities to get the search/filter result.

  1. A new resource with unique ID will be returned along with 201 Created status code. Then using this ID a GET request can be made to /api/users/ like:

    GET /api/users/?filterId=1234-abcd
    
  2. After new filter is created via POST it won't reply with 201 Created but at once with 303 SeeOther along with Location header pointing to /api/users/?filterId=1234-abcd. This redirect will be automatically handled via underlying library.

In both scenarios two requests need to be made to get the filtered results - this may be considered as a drawback, especially for mobile applications. For mobile applications I'd use single POST call to /api/users/filter/.

How to keep created filters?

They can be stored in DB and used later on. They can also be stored in some temporary storage e.g. redis and have some TTL after which they will expire and will be removed.

What are the advantages of this idea?

Filters, filtered results are cacheable and can be even bookmarked.

Opal
  • 70,085
  • 21
  • 151
  • 167
  • 5
    well this should be the accepted answer. You don't violate REST principles and you can make long complex queries to resources. It's nice, clean and bookmark compatible. The only additional drawback is the need for storing key/value pairs for created filters, and the already mentioned two request steps. – dantebarba Nov 02 '18 at 15:53
  • 4
    The only concern with this approach is, if you have date-time filters in query (or a constantly changing value). Then the number of filters to store in db (or cache) are innumerable. – Rvy Pandey Nov 12 '19 at 20:28
19

I think you should go with request parameters but only as long as there isn't an appropriate HTTP header to accomplish what you want to do. The HTTP specification does not explicitly say, that GET can not have a body. However this paper states:

By convention, when GET method is used, all information required to identify the resource is encoded in the URI. There is no convention in HTTP/1.1 for a safe interaction (e.g., retrieval) where the client supplies data to the server in an HTTP entity body rather than in the query part of a URI. This means that for safe operations, URIs may be long.

Daff
  • 41,847
  • 9
  • 99
  • 113
10

As I'm using a laravel/php backend I tend to go with something like this:

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHP automatically turns [] params into an array, so in this example I'll end up with a $filter variable that holds an array/object of filters, along with a page and any related resources I want eager loaded.

If you use another language, this might still be a good convention and you can create a parser to convert [] to an array.

joshuamabina
  • 1,210
  • 17
  • 25
the-a-train
  • 1,043
  • 12
  • 26
  • 1
    This approach looks nice, but there could be issues with using square brackets in URLs, see [what-characters-can-one-use-in-a-url](https://stackoverflow.com/questions/5649730/what-characters-can-one-use-in-a-url) – Sky Nov 18 '18 at 08:23
  • 2
    @Sky This could be avoided by URI encoding the `[` and `]`. Using encoded representations of these characters to group query parameters is a well known practice. It's even used in [JSON:API specification](https://jsonapi.org/format/#fetching-sparse-fieldsets). – jelhan Mar 04 '19 at 20:23
4

Don't fret too much if your initial API is fully RESTful or not (specially when you are just in the alpha stages). Get the back-end plumbing to work first. You can always do some sort of URL transformation/re-writing to map things out, refining iteratively until you get something stable enough for widespread testing ("beta").

You can define URIs whose parameters are encoded by position and convention on the URIs themselves, prefixed by a path you know you'll always map to something. I don't know PHP, but I would assume that such a facility exists (as it exists in other languages with web frameworks):

.ie. Do a "user" type of search with param[i]=value[i] for i=1..4 on store #1 (with value1,value2,value3,... as a shorthand for URI query parameters):

1) GET /store1/search/user/value1,value2,value3,value4

or

2) GET /store1/search/user,value1,value2,value3,value4

or as follows (though I would not recommend it, more on that later)

3) GET /search/store1,user,value1,value2,value3,value4

With option 1, you map all URIs prefixed with /store1/search/user to the search handler (or whichever the PHP designation) defaulting to do searches for resources under store1 (equivalent to /search?location=store1&type=user.

By convention documented and enforced by the API, parameters values 1 through 4 are separated by commas and presented in that order.

Option 2 adds the search type (in this case user) as positional parameter #1. Either option is just a cosmetic choice.

Option 3 is also possible, but I don't think I would like it. I think the ability of search within certain resources should be presented in the URI itself preceding the search itself (as if indicating clearly in the URI that the search is specific within the resource.)

The advantage of this over passing parameters on the URI is that the search is part of the URI (thus treating a search as a resource, a resource whose contents can - and will - change over time.) The disadvantage is that parameter order is mandatory.

Once you do something like this, you can use GET, and it would be a read-only resource (since you can't POST or PUT to it - it gets updated when it's GET'ed). It would also be a resource that only comes to exist when it is invoked.

One could also add more semantics to it by caching the results for a period of time or with a DELETE causing the cache to be deleted. This, however, might run counter to what people typically use DELETE for (and because people typically control caching with caching headers.)

How you go about it would be a design decision, but this would be the way I'd go about. It is not perfect, and I'm sure there will be cases where doing this is not the best thing to do (specially for very complex search criteria).

luis.espinal
  • 9,728
  • 5
  • 35
  • 54
  • 8
    Yo, if you (someone, whoever/whatever) things approprite to downvote my answer, would it hurt you ego to at least put a comment indicating what exactly do you disagree with? I know it's the interweebz, but ... ;) – luis.espinal Apr 10 '12 at 15:53
  • 115
    I didn't downvote, but the fact that the question starts with: "I'm currently designing and implementing a RESTful API" and your answer starts with "Don't fret too much if your initial API is fully RESTful or not" feels wrong to me. If you're designing an API you are designing an API. The question is asking how to best design the API, not about whether the API should be designed. – gardarh Oct 31 '12 at 15:23
  • 17
    The API *is* the system, work on the API first, not the backend plumbing, the first implementation could/should just be a mock. HTTP has a mechanism for passing parameters, you are suggesting it be reinvented, but worse (ordered parameters instead of name value pairs). Hence the down vote. – Steven Herod Jul 01 '13 at 05:31
  • 14
    @gardarh - yes, it feels wrong, but at times, it is pragmatic. The primary objective is to design an API that works for the business context at hand. If a fully RESTFULL approach is appropriate to the business at hand, then go for it. If it is not, then don't go for it. That is, design an API that meets your specific business requirements. Going around trying to make it RESTfull as its primary requirement is no much different from asking "how do I use the adapter pattern in X/Y problem." Don't shoe horn paradigms unless they solve actual, valuable problems. – luis.espinal Jul 01 '13 at 20:53
  • @StevenHerod - I'm not reinventing anything. A resource's URI does not include the URL parameters, ergo a need to represent a certain class of resources that might be "representable" as a collection of HTTP parameters and a base URI. There are no clean and nice approaches to do this *when you really need to do this.* How much or how little you want to represent a resource with a URI and HTTP parameters, that's specific to the problem at hand. Sometimes, YOU REALLY SHOULD NOT USE HTTP PARAMETERS TO REPRESENT A RESOURCE. Ergo, my suggestion of *an approach* (one of many) to deal with such cases – luis.espinal Jul 01 '13 at 20:59
  • Consider the following two (contrived) cases: a URI for a collection of books by author X vs a collection of books by topic Y. One could easily say "well, HTTP already has a mechanism for passing parameters", and we opt for /books?author=X and /books?topic=Y. If, on the other hand, we need RESTfull URIs, then we might need something like /books/author/X and /books/topic/Y. Then what happens when we need to represent the intersection of these two? With HTTP parameters, it is quite easy. With a RESTful requirement for complex cases, it is not that simple (/books/X/topic/Y? /books/Y/author/X?) – luis.espinal Jul 01 '13 at 21:05
  • So, there is no clean, generic answer. To pretend there is one, or to pretend that the problem does not exist because "HTTP already has a mechanism for passing parameters", that makes no sense at all. – luis.espinal Jul 01 '13 at 21:06
  • 1
    I view a resource as some collection of state, and parameters as a means for manipulating the representation of that state parametrically. Think of it this way, if you could use knobs and switches to adjust how the resource is displayed (show/hide certain bits of it, order it differently, etc...) those controls are params. If it's actually a different resource ('/albums' vs '/artists', for instance), that's when it should be represented in the path. That's what is intuitive to me, anyway. – Eric Elliott Oct 27 '13 at 06:15
  • "Get the back-end plumbing to work first". That's an inside-out approach. For me I do BDD and TDD and so I go outside in which starts with the story from the PM. Doing it this way guides you to code for the business requirement which is a much better way to go about this. And doing it this way makes sure you're coding lean and only to the behavior asked in the story. Don't go stubbing a bunch of crap out in the backend. TDD forces you to go lean by going this route..believe me. It's MORE important to think about your REST api from the outside-in perspective as they tie to requirements – PositiveGuy Jun 22 '15 at 15:47
  • I like the mature pragmatism of this answer, I think OP may find it conveys a useful approach to evaluating his/her own eventual solution. Plus, I like that this answer shows a way to do search/filter as a kind of GET, which feels a lot less pedantic that having to treat it as creating a kind of (generic, transient) resource. I want my service to focus on the persistent resources in my app domain, not ones imposed by the architectural framework. – BobHy Aug 30 '15 at 14:14
  • 1
    Funny how people will argue to death about a principle. Facebook and Netflix said the hell with it and created GraphQL and Falcor. Just because its http and an api doesn't mean it has to be REST. – user432024 Apr 28 '17 at 03:34
  • 1
    ^^ Bingo. Finally someone who gets it. – luis.espinal May 02 '17 at 16:32
  • Standardization is there for some purpose. It makes things intuitive. Otherwise every person who looks at API need to go through long description on how to decode it. Standardization should only be broken in exception cases if following it adds more cons than pros. Not sure how the URI scheme suggested is any better than the one mentioned in the question it self. I can see many standards broken without adding any value. – kishor borate Jul 02 '19 at 09:58
  • 1
    @kishorborate - and I've seen people sticking to the standards like religion without considering other requirements, losing significant value (rather than adding.) It gets tiring after a while. We do development/engineering, the act of finding middle ground when one cannot (or should not) stick to a kosher standard. Standards are guidelines for general case scenarios, nothing more. – luis.espinal Jul 02 '19 at 14:21
  • @luis.espinal i totally feel you, I see more and more almost anyone trying to write "cool" code they just read about in a book forgetting about their purpose. Most of the time it is overengineering that only brings complexity and problems. – Doru Chiulan Mar 25 '20 at 22:22
3

FYI: I know this is a bit late but for anyone who is interested. Depends on how RESTful you want to be, you will have to implement your own filtering strategies as the HTTP spec is not very clear on this. I'd like to suggest url-encoding all the filter parameters e.g.

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

I know it's ugly but I think it's the most RESTful way to do it and should be easy to parse on the server side :)

shanks
  • 852
  • 9
  • 23
  • 2
    I would not go for this approach as it has no clear arguments. The only readable argument here is filter which then has an URL encoded value. If you want to go for this approach, I would adjust this to GET api/users?mode=filter&paramA=valueA&paramB=valueB that way you could have a mode=filter, mode=search, mode=exclude, ... – Martin Nowosad Jul 01 '20 at 21:11
  • This is useful in case where there is huge amount (n) of possible params (which is not problem for front), but in backend with proposed approach you have just one param (filter) instead of n(huge num) optional params or dynmaic param handling. – k4hvd1 Dec 17 '20 at 10:03