I am designing a RESTful interface for somewhat hierarchical data:
course > module > problem > solution
But I cannot decide what the URL for accessing the "solution" resource should be. These are the two options I am choosing between, for example, to retrieve the solution with globally unique PK=3:
/courses/2/modules/5/problems/2/solutions/3
/solutions/3/
Any advice on which of the two is preferable?
Whenever I have to come up with URLs I always ask myself (in this context)
Can a solution resource exist without a problem, module or course?
Yes -> /solutions/3
No -> /courses/2/modules/5/problems/2/solutions/3
If a solution needs a problem to exist, i.e. it belongs to a problem, it makes little sense to use the short url.
Another way to lok at it:
On the backend, do you need a reference to a problem in order to be able to fetch a solution?
No -> /solutions/3
Yes -> /courses/2/modules/5/problems/2/solutions/3
Related
It is straightforward to put resource id into url if it is a int or long type. e.g.
GET files/123
But my problem is that my resource identifier is a path. e.g. /folder_1/folder_2/a.sh because the underlying implementation is a filesystem. So I can not put it as part of rest api url because it is conflict with url path.
Here's approaches what I can think of:
Put the path id as the request param. e.g.
GET files?path=/folder_1/folder_2/a.sh
Encode/decode the path to make it qualifier as part of url.
Introduce another int/long id for this resource in backend. And map it to the path. The int/long type resource id is stored in database. And I need to maintain the mapping for each CURD operation.
I am not sure whether approach 1 is restful, approach 2 needs extra encoding/decoding, and approach 3 needs extra work to maintain the mapping.
I wonder what is the best practice to design the rest api url for this kind of case.
Simple:
#GET
#Path("/files/{path:.+}")
#Produces({MediaType.TEXT_PLAIN})
public String files(
#PathParam("path") String path
) {
return path;
}
When you query files/test1/tes2 via url output is:
test1/tes2
Just put the path after a prefix, for example:
GET /files/folder_1/folder_2/a.sh
There isn't a conflict, since when the request path starts with your known prefix (/files/, in the above example), you know that the rest should be parsed as the path to the file, including any slashes.
Well, my experience designing "restful" APIs shows that you have to take into consideration future extensions of your API.
So, the guidelines work best when followed closely when it makes sense.
In your specific example, the path of the file is more of an attribute of the file, that can also serve as its unique ID.
From your API client's perspective, /files/123 would make perfect sense, but /files/dir1/file2.txt is debatable.
A query parameter here would probably help more, much like what you would do if you wanted to retrieve a filtered list of files, rather than the whole collection.
On the other hand, using a query parameter would also help for future extensions, since supporting /files/{path} would also mean conflicts when attempting to add sub-resources to your files endpoint.
For example, let's assume that you might need in the future another endpoint /files/attributes. But, having such an endpoint, would exclude any possibility for your clients to match a file named attributes.
Assume you have a REST service that already gets users by id, so the url looks something like
GET /users/{userId}
But you want to create a duplicate web service that gets users by email, so:
GET /users/{email}
Which is better?
Method 1:
Same method:
/users/{input}
...
if(isEmail(input)) queryByEmail(input);
else queryById(input);
Method 2:
Different Method:
GET /users/{userId}
GET /usersByEmail/{email}
Since there is no actual overlap between email addresses and IDs. I would just use same endpoint for both. Especially if GET /users/{id} is already a published interface.
So, I would go with 1st method.
GET /users/{identifier}
Then on the API server you have to add a small check, whether {identifier} is a number or not.
I would also like to note, that "pretty URLs" do not make it REST :) You probably will want to watch this lecture: https://www.youtube.com/watch?v=pspy1H6A3FM
My personal preference would be,
GET /users/id/{id}
GET /users/email/{email}
But it all depends on what you the rest endpoints to look like.
Which is better?
REST doesn't care; from the perspective of the client, the URI is opaque. The clients concerns are following links/submitting forms/completing templates.
Information encoded into the URI is done at the server's discretion and for its own exclusive use.
So you can use any spelling you like. As a rule, it's a good idea to conform to local spelling conventions (in much the same way that your variable names in code should conform to your coding conventions). But your clients shouldn't need to know the details of those conventions.
/users/{input}
...
if(isEmail(input)) queryByEmail(input);
else queryById(input);
Note that you aren't necessarily deeply committed to one approach; that's part of the point of decoupling the identifiers from the representations. For instance, your implementation could just as easily look like
/users/{input}
...
if(isEmail(input)) redirectTo(/users/email/{input});
else redirectTo(/users/id/{input});
which allows clients that have bookmarked the original URI to arrive at the correct resource.
I have 3 models/resources:
Model A
Model B
Model C that belongs to Model A and Model B
Then, I can build my API routes like this:
/api/a-resources/x/c-resources
/api/b-resources/x/c-resources
Or maybe I can do:
/api/c-resources?a_resource_id=x
/api/c-resources?b_resource_id=x
to get similar behaviour...
The question is:
what do I need to ask myself to choose between these options?
Perhaps think about how is your API going to be consumed, what's the workflow for the consumer?
if it's first going to navigate to resource A/B, and only then he might request C, the first option seems a better fit;
however, if there's also a scenario where he might directly need resource C, then you can go with the latter option.
You can obviously go with either of those, there's no wrong one, things are rarely black and white in REST.
Let's say I want to make a RESTful interface, and I want to work with foos based upon their IDs. Nothing new here:
GET /api/foo1 returns a representation (e.g. using JSON) of foo1.
DELETE /api/foo1 deletes foo1.
etc.
Now let me tell you that a "foo" is a collection type thing. So I want to be able to add a "bar" to a "foo":
PUT /api/foo1/bar3 adds bar3 to foo1.
GET /api/foo1/bar3 returns a representation of foo1.
DELETE /api/foo1/bar3 removes bar3 from foo1.
DELETE /api/foo1 deletes foo1 altogether.
Now the question remains: what does GET /api/foo1 do? Does it it simply return a representation of foo1 as I originally assumed in this question? Or does it return a list of bars? Or does it return a representation of foo1 that is both a description of foo1 as well as includes a list of all contained bars?
Or should GET /api/foo1 merely return a representation of foo1 as I assumed at the beginning, and require a PROPFIND request to list the bars inside foo1 (the approach taken by WebDAV)? But then in order to be consistent, wouldn't I have to change all my other list-type functionality to PROPFIND, directly contradicting all those thousands of RESTful tutorials that say to use GET /api/foo1 to list the contents?
After some pondering, I think the best conceptual explanation from a RESTful perspective is that usually the "thing" is not the same thing as its "collection". So while in the WebDAV world a directory/ might be the same thing that holds its files, in the RESTful world I might have a separate directory/files/ subpath for the contained files. That way I can manipulate directories separately from the files that hold.
Consider a RESTful API for a farm which contains barns. The endpoint farm/api/barns/ might return a list of barns, one of which would be farm/api/barns/bigredbarn. Naively I would think that retrieving farm/api/barns/bigredbarn/ would provide me a list of animals in the barn, which is what prompted this question.
But really the animals in the barn is only one aspect of the Big Red Barn. It might contain vehicles and hay:
farm/api/barns/bigredbarn/animals/
farm/api/barns/bigredbarn/vehicles/
farm/api/barns/bigredbarn/haybales/
With this approach the dilemma I faced does not arise.
The semantics of webdav has never really been reconciled with the idioms of RESTful interfaces.
In theory, GET should retrieve a representation of a state of a resource and PROPFIND should be used to retrieve the members of a collection.
So you should do this:
GET /api/foo1/ - returns state of foo1 only
PROPFIND /api/foo1/ - returns the members of foo1
Most front end devs would freak out if you told them to use PROPFIND, although its completely supported in browser js implementations.
Personally i use a webdav/json gateway, where requests are made using RESTful idioms, but routed to my webdav implementation
For example i would do this:
GET /api/foo1/_PROPFIND?fields=name,fooProp1,fooProp2
And that would return
[
{ name : "bar1", fooProp1: "..", fooProp2 : ".."},
{ name : "bar2", fooProp1: "..", fooProp2 : ".."}
]
One advantage of this approach is that client's get to control the json properties returned. This is good because a rich API will have a lot of properties, but in most situations clients dont need all of them.
The routes and their operations in RESTfull API are completely designed by the developers. It's the developer who decides what to return while requesting a specific route say, GET /api/foo1.
And the developer should design every route including /api/foo1/bar. There is no specific rule on what a particular route should do. If your API is an open-source project make a clean and clear documentation of every route.
Don't waste your time thinking about the old school strategies.
I'm wondering where to put the edit, and new keywords by rest application. I'll use express-resource in a project, and the default settings are these:
GET /forums -> index
GET /forums/new -> new
POST /forums -> create
GET /forums/:forum -> show
GET /forums/:forum/edit -> edit
PUT /forums/:forum -> update
DELETE /forums/:forum -> destroy
There is a problem with this solution: there is not a real resource behind the new and edit. I mean the URL-s refer to resources, and after any slash is a sub-resource.
For example:http://my.example.com/users/1 represents:
var firstUser = {
name: "John Smith",
birthDate: new Date(1952,10,4),
hobbies: ["skiing", "football"],
...
}
And http://my.example.com/users/1/birthDate represents:
firstUser.birthDate
But by http://my.example.com/users/1/edit there is no such property:
firstUser.edit
So something is wrong with this conception.
Where is the real place of these keywords? In queryString or in headers?
From the perspective of a REST API these do not exist anywhere as they are not related to the representation of resources. They are actions upon resources and therefore expressed by the HTTP methods used. They would not bee needed if you were to create an external client that uses the API.
There is likely a need to provide some support for this type of functionality so that something like a UI could be presented, but that is the concern of the particular application and not the API itself. At that point it becomes discretionary but I would certainly avoid using headers as that would be pretty outside of conventional practice. But by headers it appears that you actually meant URI path. Out of those 2 I would say the path is the better option since it clearly defines any type of UI as a distinct resource and would keep it apart from the clean API, while using a query string on a part of the API would more tightly (mistakenly) associate it with the underlying resource.
Some update after a year or so:
edit and new should be hyperlinks
these links are just the representations of possible operation calls
by following them it is possible to manipulate the resource they belong by sending representations of the intended resource state, and/or by calling the proper methods
these terms are no resources, they don't have their own URL (resource identifier)
Thanks the advices Matt Whipple!
I think the best way to learn REST is reading the Fielding dissertation. There are many tutorials out there, but the authors of most of these does not really understand REST.