Pass multiple filters with multiple values in a query string (REST) - rest

I originally posted multiple filters containing multiple values in JSON as part of my GET request but I believe this is bad practise, so I changed it to a POST but I don't like it as getting results from a query has nothing to do with a POST so I guess I'll have to use a query string
Most filter examples I have found are either using one filter or one value, but I am looking as to whether or not there is a best practise to pass multiple filters with multiple values for filtering as a single parameter in the query string.
For example, this is a basic one which looks for all cars that are red
GET /cars?color=red
But what if I wanted to look for all cars that are
red, blue or green and
have 2 seats or less and
their brand name starts with b and
can be bought in the US, UK or Germany
Would the following be ok?
http://myserver/api/cars?color=red|blue|green¬seats<=2¬brand[startswith]b¬country=USA|UK|Germany
I'm suggesting the use of the:
| character as a separator between each values for a given filter
¬ character as a separator between each filters
[startsWith] to handle the search type, but could contain [=, <=, >=, <>, [contains],[endswith], etc...
This would then be parsed in the server end and the relevant filters would be build accordingly based on the provided values
Hope this make sense but I'm really interested as to whether or not there is a standard/best practise used for such scenarios with REST in mind?
Thanks.

As in most design questions, the key is having a consistent design for all your APIs. You can follow certain well-known guidelines/standards to make your API easily discoverable.
For example, take a look at OData. The "Queries" section on this page is relevant to your question. Here's an example:
https://services.odata.org/v4/TripPinServiceRW/People?$top=2 & $select=FirstName, LastName & $filter=Trips/any(d:d/Budget gt 3000)
Another option is the OpenSearch standard. The relevant section is here. Here's an example:
https://opensearch.php?recordSchema=html&query=dc.creator any Mill* Grad*
Another interesting option is GraphQL, which makes it easier to map query parameters to data fetch parameters. It uses a filter payload instead of query parameters. See the spec here: GraphQL Spec.

Related

REST API filter queries - handling OR operations

I am trying to create a nice helper class to parse some query parameters so they can be used to filter an array of data.
I am using the following syntax for my query parameters:
?filter[name,contains,string]=foo // name field contains the string "foo"
?filter[id,gte,number]=123 // item field is greater then or equal to 123
?filter[type,eq,string]=foo|bar // type field is equal to "foo" or "bar"
(you can also use , between the values to act as an AND operator, but on a single field, it doesn't have many use cases eg gt 1 AND 2 isn't a great filter)
These query params can be combined, eg
?filter[name,contains,string]=foo&filter[id,gte,number]=123
And this will act as an AND condition, meaning data is returned that matches both filters.
Great so far. But I want to implement an OR condition for multiple separate filters.
?filter[]=... is used for the actual filter query, so I can't really use that to set the OR condition.
I've tried finding REST API implementations that use somethig similar to this filtering syntax, the closest is Laravel, but the docs don't go into any details about it.
Hoping someone can point me to a good resource about REST API filtering/help me figure out a nice way to implement the OR condition.
Unfortunately I think if you want the flexibility you're looking for, it'll probably be best to define a proper grammar and accept a "filter string" that follows that syntax. This would mean you have to define the grammar and behavior of defaults, parse it, and turn it into a query that matches your underlying storage system (e.g., a SQL query or ORM API calls).
For example, this might make your filters look something like:
?filter=name:("foo" AND id >= 123) OR id <= 567
And it turns out that there's quite a lot to decide when you start introducing complex filtering operations.
For more reading, Google's API filtering syntax and guidelines are: https://google.aip.dev/160 and the EBNF grammar is https://google.aip.dev/assets/misc/ebnf-filtering.txt

REST Protocol for searching and filtering

The standard REST verb for returning a value GET can take different parameters to select what to "get". Often there is one that takes an id to get a single value, and often some sort of search criteria to get a list.
Is there a standard way to specify the filtering and sorting of the data that is being searched for? For example, if I have an invoice record I'd like to write a GET query that says "give me all invoices for customer 123, with total > $345 and return in descending order of date".
If I were writing this myself I'd have something like:
GET http://example.com/mydata?query="customer=123&&total>345.00"&order="date"
(Note I didn't urlencode the url for clarity, though obviously that is required in practice, but I hope you get what I mean.)
I can certainly write something for this, but I am wondering if there is a standardized way to do this?
Is there a standard way to specify the filtering and sorting of the data that is being searched for?
Not that I'm aware of.
Note that HTTP doesn't really have queries (yet); HTTP has resource identifiers.
We've got a standard for resource identifiers (RFC 3986) and a standard for URI templates (RFC 6570) that describes how to produce a range of identifiers via variable expansion.
But as far as I can tell there is no published "standard" that automatically transforms a URI into a SQL query.
It's possible that one of the "convention over configuration" frameworks (ex: Rails) might have something useful here, but I haven't found it.

How to construct REST API endpoints with both composite keys and arrays?

Although there are tons of similar questions regarding the REST API design, I am asking a very specific question that I could not found answers in other similar questions.
Suppose that I am trying to GET a list of devices in the database with Building_Type and Room_Type filters. I would like to pass an array of filters, and each filter contains two field as a composite key. I've found standard practice to pass parameter arrays, but I could not find a good way for composite keys in the array.
Example:
GET /api/v1/devices?building_type=Educational&room_type=Office
This GETs all rooms with Educational building type and Office room type. However, I am trying to get a list of rooms for multiple composite combinations of {building_type, room_type}.
I am thinking of something like the following:
GET /api/v1/devices?location[]={building_type=Educational,room_type=Office}&location[]={building_type=Commercial,room_type=Office}&location[]={building_type=Educational,room_type=Classroom}
However this doesn't look like standard practice. I am asking for a better way to design this endpoint. I also don't want POST because this query does not change the state
on the server.
Note:
Please note that the following is incorrect, because I need to filter by an array of composite attributes of {building_type, room_type}.
GET /api/v1/devices?building_type[]=Educational&building_type[]=Commercial&room_type[]=Office&room_type=Classroom
It depends on what your backend can handle, but I would try an array of objects, like:
GET /api/v1/devices?location[][building_type]=Educational&location[][room_type]=Office&location[][building_type]=Commercial&location[][room_type]=ClassRoom
Rails 6 parses this like I expect:
"location"=>[{"building_type"=>"Education", "room_type"=>"Office"}, {"building_type"=>"Commercial", "room_type"=>"ClassRoom"}]
But, as this article goes into, libraries don't handle complex object serialization/deserialization into query params consistently. If your backend doesn't like the above, numerically indexing the array should work (though it's more work to construct from your client code):
GET /api/v1/devices?location[0][building_type]=Educational&location[0][room_type]=Office&location[1][building_type]=Commercial&location[1][room_type]=ClassRoom
If you want something that won't be implementation-dependent, you could also consider URL-encoding a JSON string that represents your search query:
GET /api/v1/devices?query=%7B%22locations%22%3A%20%5B%7B%22building_type%22%3A%20%22Educational%22%2C%20%22room_type%22%3A%20%22Office%22%7D%2C%20%7B%20%22building_type%22%3A%20%22Commercial%22%2C%20%22room_type%22%3A%20%22Office%22%7D%5D%7D
Not pretty, but possibly less frustrating.

REST where should end point go?

Suppose there's USERS and ORDERS
for a specific user's order list
You could do
/user/3/order_list
/order/?user=3
Which one is prefered and why?
Optional parameters tend to be easier to put in the query string.
If you want to return a 404 error when the parameter value does not correspond to an existing resource then I would tend towards a path segment parameter. e.g. /customer/232 where 232 is not a valid customer id.
If however you want to return an empty list then when the parameter is not found then query string parameters. e.g. /contacts?name=dave
If a parameter affects an entire URI structure then use a path e.g. a language parameter /en/document/foo.txt versus /document/foo.txt?language=en
If unique identifiers to be in a path rather than a query parameter.
Path is friendly for search engine/browser history/ Navigation.
When I started to create an API, I was thinking about the same question.
Video from apigee. help me a lot.
In a nutshell when you decide to build an API, you should decide which entity is independent and which is only related to someone.
For example, if you have a specific endpoint for orders with create/update/delete operations, then it will be fine to use a second approach /order/?user=3.
In the other way, if orders have only one representation, depends on a user and they don't have any special interaction then you could first approach.
There is also nice article about best practice
The whole point of REST is resources. You should try and map them as closely as possible to the actual requests you're going to get. I'd definitely not call it order_list because that looks like an action (you're "listing" the orders, while GET should be enough to tell you that you're getting something)
So, first of all I think you should have /users instead of /user, Then consider it as a tree structure:
A seller (for lack of a better name) can have multiple users
A user can have multiple orders
An order can have multiple items
So, I'd go for something like:
The seller can see its users with yourdomain.com/my/users
The details of a single user can be seen with yourdomain.com/my/users/3
The orders of a single user can be seen with yourdomain.com/my/users/3/orders
The items of a single order can be seen with yourdomain.com/my/users/3/orders/5

RESTful URI for Filtering Nested Collections

I'm looking for a good way to form a URI for a resource that filters on a collection of values contained within records. For example, say you have a recipe database and you want to search for recipes that contain "cherry" (obviously most recipes would contain multiple ingredients).
If I just want to filter on single values, I could do something similar to the following:
/recipe/search/?name=Spaghetti
But what about filtering on multiple values? I was considering something like the following:
/recipe/search/?ingredients=contains=cherry
Any thoughts on this? Is there a "standard" for a filter of this kind?
Update: One problem I have with my idea is the way it gets handled on the backend (in my case Rails). When querying the server with this particular format, Rails generates a Ruby hash that could get ugly like the following:
{"ingredients"=>"contains=cherry",
"action"=>"search",
"controller"=>"recipe"}
Your URI
First of all, in contrast to other answers I'll start from a REST perspective and then find appropriate additions to it. I am not strong in Ruby so bear with no-code on the backend.
Recipies are the entities you wanna present
your users find them at /recipies
HTTP has QUERYS for filtering
wanna have sorted those recipies by date? use /recipies/?sortby=date&sort=asc
Only recipies with cherries goes similarly: /recipies/?ingredients=cherry
So that's the REST way of structuring your basic URL.
For multiple matches there is no official way to do it, but i'd follow user1758003. This is an intuitive construction of the url and easily to parse on the backend, so we have /recipies/?ingredients=cherry,chocolate
Don't forget /recipies/search is not restful because it mirrors recipies and does not represent an independent entity. However it is a great place to host a searchform for visitors to your site.
Now there are some additional questions packed into the first, let me address them one by one:
I have a website consuming this api, how should the search form look like?
Give your visitors a /recipie/search page or a quick filtering possibility on /recipies.
Just set the <form action="/recipies" type="GET">. The browser will add all parameters as an Query string after /recipies.
Advanced functionality
A request to /recipies should list all. If you add a query every parameter of the query must be respected. so /recipies/?ingredients=cheese MUST only return cheesy recipies.
For multiply query parameter values there is no fixed standard but I'd like a service to be intuitive.
I read GET /recipies/?ingredients=cherry,chocolate as Get me the recipies which have ingredients of cherry and chocolate. To get a list of recipies containing either cherry or chocolate I'd want to write the URI like /recipies/?ingredients=cherry|chocolate which makes it visually very different from a comma and has a predefined meaning (OR) in programming contexts.
I'm not familiar with the specifics of ruby hashes but I'm guessing the hash is created to uniquely identified the query both at the http and data access levels?
Regardless, you want to be careful about URL encoding if you wish to use json in a query parameter. Another thing to keep in mind is the term "search" could be considered repetitive. If your server is being accessed using a GET method and you have criteria then hopefully you're not modifying any state in the back end. Not your question but just thought I'd mention it.
So...
https://yourserver.com/approot/recipe/search?ingredients=cherry,cheese&type=cake
HTTP doesn't define commas as a query string separator so your framework should be able to parse 2 query string parameters:
ingredients: "cherry,cheese"
which you should be able to easily covert to an an array using split or whichever equivalent function ruby provides.
and
type: "cake" (extra query term added to illustrate a point and because cherry cheese cake is awesome and cake is not an ingredient)
If I understand your example correctly you would end up with:
{
"ingredients"=>"cherry,cheese",
"type"=>"cake",
"action"=>"search",
"controller"=>"recipe"
}
Is this what you where looking for?
Most of the REST webservice using JSON data only.So use JSON format which will return single string value only. From this JSON format you can send the array value also.
For array value means you to convert that array into the JSON format like this
from php:
$ingredients = array('contains'=>array('fruits'=>'cherry,apple','vitamins'=>'a,d,e,k'));
$ingredients_json = json_encode($ingredients);
echo $ingredients_json;
it will return json format like this:
{"contains":{"fruits":"cherry,apple","vitamins":"a,d,e,k"}}
and you can use this JSON string in the url
/recipe/search/?ingredients={"contains":{"fruits":"cherry,apple","vitamins":"a,d,e,k"}}
in the server side we have the option to decode this JSON format value to the array.
{"ingredients"=>"{\"contains\":{\"fruits\":\"cherry,apple\",\"vitamins\":\"a,d,e,k\"}}",
"action"=>"search",
"controller"=>"recipe"}