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

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.

Related

Pass multiple filters with multiple values in a query string (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.

How to properly access children by filtering parents in a single REST API call

I'm rewriting an API to be more RESTful, but I'm struggling with a design issue. I'll explain the situation first and then my question.
SITUATION:
I have two sets resources users and items. Each user has a list of item, so the resource path would like something like this:
api/v1/users/{userId}/items
Also each user has an isPrimary property, but only one user can be primary at a time. This means that if I want to get the primary user you'd do something like this:
api/v1/users?isPrimary=true
This should return a single "primary" user.
I have client of my API that wants to get the items of the primary user, but can't make two API calls (one to get the primary user and the second to get the items of the user, using the userId). Instead the client would like to make a single API call.
QUESTION:
How should I got about designing an API that fetches the items of a single user in only one API call when all the client has is the isPrimary query parameter for the user?
MY THOUGHTS:
I think I have a some options:
Option 1) api/v1/users?isPrimary=true will return the list of items along with the user data.
I don't like this one, because I have other API clients that call api/v1/users or api/v1/users?isPrimary=true to only get and parse through user data NOT item data. A user can have thousands of items, so returning those items every time would be taxing on both the client and the service.
Option 2) api/v1/users/items?isPrimary=true
I also don't like this because it's ugly and not really RESTful since there is not {userId} in the path and isPrimary isn't a property of items.
Option 3) api/v1/users?isPrimary=true&isShowingItems=true
This is like the first one, but I use another query parameter to flag whether or not to show the items belonging to the user in the response. The problem is that the query parameter is misleading because there is no isShowingItems property associated with a user.
Any help that you all could provide will be greatly appreciated. Thanks in advance.
There's no real standard solution for this, and all of your solutions are in my mind valid. So my answer will be a bit subjective.
Have you looked at HAL for your API format? HAL has a standard way to embed data from one resources into another (using _embedded) and it sounds like a pretty valid use-case for this.
The server can decide whether to embed the items based on a number of criteria, but one cheap solution might be to just add a query parameter like ?embed=items
Even if you don't use HAL, conceptually you could still copy this behavior similarly. Or maybe you only use _embedded. At least it's re-using an existing idea over building something new.
Aside from that practical solution, there is nothing in un-RESTful about exposing data at multiple endpoints. So if you created a resource like:
/v1/primary-user-with-items
Then this might be ugly and inconsistent with the rest of your API, but not inherently
'not RESTful' (sorry for the double negative).
You could include a List<User.Fieldset> parameter called fieldsets, and then include things if they are specified in fieldsets. This has the benefit that you can reuse the pattern by adding fieldsets onto any object in your API that has fields you might wish to include.
api/v1/users?isPrimary=true&fieldsets=items

REST: Filter primary resource by properties on related resource

I'm looking for some guidance/advice/input on the concept of filtering resources when making a REST API call. Let's say I have Users and Posts, and a User creates a Post. If I want to get all Posts, I might have a route as follows:
GET /api/posts
Now if I wanted to get all posts that were created after a certain date, I might add a filter parameter like so
GET /api/posts?created_after=2017-09-01
However, let's say I want to get all posts by Users that were created after a certain date. Is this the right format?
GET /api/posts?user.created_after=2017-09-01
When it comes to filtering, grouping, etc, I'm having a hard time figuring out the right stuff to do for REST APIs, particularly when using a paginated API. If I do this client side (which was my initial thought) then you potentially end up with a variable number of resources per page, based on what meets your criteria. It seems complicated to add all of this logic as query parameters over the API, but I can't see any other way to do it. Is there a standard for this kind of thing?
There is no objective 'right' way. If using user.created_after logically makes sense in the context of your API, then there's nothing really wrong with it.
Personally, I would not use user.created_after.
I would rather prefer one of the following options:
Option I: /api/posts/users/{userid}?created_after=2017-09-01
Option II: /api/posts/?user={userid}&created_after=2017-09-01
The reason is simple: It looks wrong to me to create dynamic query parameters. Instead you can combine the query parameters (Option II) or even define a more specific resource (Option I).
Regarding pagination: the standard approach is something like this: In addition to filter parameters, you define the following parameters: page and pageSize. When constructing the request, client will specify something like page=2&pageSize=25&orderBy=creationDate.
It's important to note that server must always validate the parameters and can potentially ignore or override incorrect parameters (e.g. page doesn't exist, or pageSize is too big may not return an error, but instead returning reasonable output. This really depends on your business case)

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"}

REST parameters vs URI

I'm just learning REST and trying to figure out how to apply it in practice. I have a sampling of data that I want to query, but I'm not sure how the URLs are meant to be formed, i.e. where I put the query. For example, for querying the most recent 100 data records:
GET http://data.com/data/latest/100
GET http://data.com/data?amount=100
which of the previous two queries is the better, and why? And the same for the following:
GET http://data.com/data/latest-days/2
GET http://data.com/data?days=2
GET http://data.com/data?fromDate=01-01-2000
Thanks in advance.
Personally, I would use the query string format in this case. If your /data path is returning all of the data, and you would like to perform this type of query, I believe it makes the most sense. You could also pass query string parameters such as ?since=01-01-2000 to get entries after a specified date or pass column names such as ?category=clothing to retrieve all entries with category equaling clothing.
Additionally, you would want paths such as /data/{id} to be available to retrieve certain entries given their unique id.
It really depends on a lot of things. If you're using any sort of MVC framework, you'd use the URI segments to define your get request to your API which I personally prefer.
It's not a big deal either way, it's all based on preference and how predictable you want the URL to be to your user. In some cases, I'd say go with the REST parameters, but more often than not a URI based GET is quite clean if your setup supports it.