Related
I came across HATEOAS on my researches and was thinking : doesn't HATEOAS multiplicate HTTP requests ?
Let's take the basic customer and order example.
Let's say you want to retrieve an order, the endpoint would be /orders/2
with the following JSON response :
{
"id": 2,
"total": 50.00,
"links": [{
"rel": "customer",
"href": "http://api.domain.com/customer/1
}]
}
Now what if I also need the customer ? Do I have to make another request to /customer/1 ? Doesn't this overload the HTTP traffic ?
Couldn't I get the couple customer + order with a single endpoint like /customers/1/orders/2 ?
Or just send the customer in the /orders/2 JSON response ?
{
"id": 2,
"total": 50.00,
"customer": {
"id": 1,
"name": "Dylan Gauthier"
}
}
What's the benefit(s) of one solution or another ? When do I need one or the other ?
Thanks ! :-)
If the server only supplies the customer and order separately, then you have to make two requests regardless of whether they are following REST or not.
Nothing about REST or its HATEOAS constraint prevents the server from providing both customer and order in the same resource, exactly as you have suggested:
GET /orders/2
{
"id": 2,
"total": 50.00,
"customer": {
"name": "Dylan Gauthier"
}
}
But the customer in that response has no connection to the identifier /customers/1 — the server could combine the two ideas:
{
"id": 2,
"total": 50.00,
"links": [{
"rel": "customer",
"href": "http://api.domain.com/customer/1
}],
"resources": {
"http://api.domain.com/customer/1": {
"name": "Dylan Gauthier"
}
}
}
or better yet, group the links by their relation to the requested resource:
{
"id": 2,
"total": 50.00,
"links": {
"customer": [{
"href": "http://api.domain.com/customer/1"
}]
},
"resources": {
"http://api.domain.com/customer/1": {
"name": "Dylan Gauthier"
}
}
}
Whilst this would make it a bit more work for the client to print the name of the customer (nothing at all taxing, mind), it allows the client to fetch more information about the customer if they want to!
Just to add to Nicholas' answer:
Embedding related resources
Pros: saves you a trip to the server
Cons: While it saves you a trip the first time and may be a few lines of code, you are giving up on caching: if something changes in a related resource (that you embedded) client cache is no more valid, so the client has to make the request again. Of course, assuming you leverage HTTP caching. Which you should...
If you want to go this route, you are better off using something like GraphQL... but wait!
Going "pure" HATEOS
Pros: resources have independent life-cycles; easier to make each (type of) resource evolve without impacting the others. By fully leveraging the cache, overtime, the overall performance is far better.
Cons: more requests (at first access), this might be a little slower on first access; some more code to manage the HATEOS thing...
I personally tend to use the second approach whenever possible.
The classic web analogy:
If it can help, a classic website is just another api that serves html related resources, the client app being the browser itself. If you have ever done some html/css/js, you might want to approach it the same way:
For the given particular website, given its navigation architecture...etc would you rather inline all/part of the css/js (the related resources) in the html pages (the main resource) or not.
Currently, I'm working on new product and making REST API for both - public and internal needs. I started with {json:api} specification and I was pretty happy with it until I faced some questions I cannot find answers to.
According to JSON API specification, every resource MUST contain id.
http://jsonapi.org/format/
Every resource object MUST contain an id member and a type member. The values of the id and type members MUST be strings.
And that's fine in many cases but not all.
Most of our endpoints are about "resources"
If I ask for a "things" collection (http://example.com/things)
{
"data": [{
"type": "things",
"id": "1",
"attributes": {
"title": "first"
},
"links": {
"self": "http://example.com/things/1"
}
}, {
"type": "things",
"id": "1",
"attributes": {
"title": "second"
},
"links": {
"self": "http://example.com/things/2"
}
}]
}
If I ask for a single "things" resource (http://example.com/things/1)
{
"data": {
"type": "things",
"id": "1",
"attributes": {
"title": "first"
},
"links": {
"self": "http://example.com/things/1"
}
}
}
But what to do with endpoints which are not about resources and does not have ID?
For example, in our application, there is an endpoint http://example.com/stats which should return stats of current logged in user. Like
{
"active_things": 23,
"last_login": "2017"
}
There is no id for this "resource" (it's not actually a resource, is it?). Backend just collects some "stats" for logged in user and returns an object of stats. There many endpoints like this in this application, for example, we have Notification center page where the user can change email addresses for different notifications.
So frontend app (single-page-app) first has to get current values and it sends the request to GET http://example.com/notification-settings.
{
"notifications_about_new_thing": "arunas#example.com",
"notification_about_other_thing": "arunas#example.com"
}
And there are many more endpoints like this. The problem is - how to return these responses in JSONAPI format? There is no ID in these endpoints.
And the biggest question is - why nobody else is facing this issue (at least I cannot find any discussion about this)? :D All APIs I ever made has some endpoints which don't have "id".
I have two ideas, first is to fake id, like "id": "doesnt_matter", the second - do not use json-api for these endpoints. But I don't like both of them.
Think RESTfully and everything can (must) be a resource. There is no "logged in" user as there are no sessions in RESTful APIs as they are stateless. There's no session state maintained between REST API invocations, so you have to be explicit about who the user is.
In this case, the resource is the user who has some stats attributes (in the simple case) or perhaps a relationship to a separate stats relationship (more complicated, not shown):
GET /users/1234
{
"data": {
"type": "users",
"id": "1234",
"attributes": {
"name": "etc.",
"active_things": 23,
"last_login": "2017"
}
}
}
I'm no JSON API expert- but it's worth noting that while JSON API is a concrete specification, it is not the same thing as JSON, nor as a REST API. If you don't like its semantics, I agree with commenters who argue, "Don't use it." If you are going to use JSON API, do so in a compliant way, where every response is a resource; every resource has an ID and a type; and additional information is supplied as attributes of the resource.
Toward your question, I'm thinking about something similar where my application returns computation results. Now on the one hand, these are not strictly "resources" and so I've been toying with the idea of returning the raw result as an array (which I believe would be valid JSON, with a caveat), e.g:
[ 47 ]
On the other hand, there is the idea that the results are the results of a computation that the client specified RESTfully, in which case one of the following two cases is likely true:
The same request submitted later is likely to have the same result. This suggests that in fact the result really is a resource.
The same request submitted later is likely to have a different result. This suggests that the client may want to track how results change for various queries, and so at least the query parameters should be part of the response.
In both cases, the response really is a 'result' object, and even though it doesn't have an ID per se, it does have an identity. If nothing else fits, the ID could be the query that generated the response.
This seems RESTful to me. User #n2ygk suggests that this is not correct as regards the JSON API spec, that an ID should simply be a unique ID and not have another semantic interpretation.
I'd love to hear other perspectives.
We have an existing web application which has an API not based on REST. We'd like to put a REST API in front of it, using Strongloop, however, getting lost in the documentation and not sure if this can be achieved.
Example:
Want to configure an endpoint in Strongloop which looks like;
localhost:3000/api/DataObject/Orders?StartDate=01/01/2016&EndDate=31/01/2016
A GET on this end point should service the request from our existing web application, where the URL would like;
localhost:4000/wh?Page=ObjectBuilder&Name=Orders&StartDate=01/01/2016&EndDate=31/01/2016
i.e. take Orders from the API request and insert into the remote URL, along with the remaining parameters.
I could code this using express.js, but was wondering if this is possible using configuration in Strongloop?
Thanks!
I think you might be able to use the built-in REST connector even though your legacy API is not REST per se (although you don't get all the benefits of the built-in mapping to find, create, destroy, etc). The connector simply translates URLs into model methods. That said, I think you do need to have the old API spit out JSON... does it do that? If not, then you basically just have to write a full translator.
This is not working code, but might help you get part of the way there.
In your server/datasources.json file:
"old-service": {
"name": "old-service",
"connector": "rest",
"operations": [{
"template": {
"method": "GET",
"url": "http://localhost:4000/wh",
"headers": {
// whatever you might need to send...
},
"query": {
"Page": "ObjectBuilder",
"Name": "{name}",
"StartDate": "{start}",
"EndDate": "{end}"
},
"responsePath": "$.results.theObject" // be sure to custom ize this
},
"functions": {
"buildObject": ["name", "start", "end"]
}
}]
}
In your server/model-config.json be sure too map your DataObject model to this datasource:
{
// ...
"DataObject": {
"public": true,
"dataSource": "old-service"
},
}
And in your model itself (common/models/DataObject.js) you can now call the buildObject() method:
DataObject.buildObject('Order', '01/01/2016', '31/01/2016', function(err, result, response) {
if (err) { ... }
// otherwise look at the result or response...
});
Now that you can call this method, you could put it into a remoteMethod or even override the default find method for this model.
Good luck, but in many of these cases you simply have to write the "conversion" code yourself. Might be easier to rewrite the API from scratch. ;)
If I wanted to create (POST) a new resource linking two independent resources, what is the most proper - with respect to HATEOAS and REST principles - way to structure the entity of the request?
Any references in RFCs, W3C documents, Fielding's thesis, etc., about the proper way for a client to request two independent resources be linked together would be most valuable. Or, if what I'm interested in is simply outside the scope of REST, HATEOAS, an explanation of why would also be great.
Hopefully my question above is clear. If not, here's a scenario and some background to ground the question.
Let's say I have two independent resources: /customer and /item, and a third resource /order intended to the two.
If I'm representing these resource to the client in a HATEOAS-like way (say with JSON-LD), a customer might (minimally) look like:
{
"#id": "http://api.example.com/customer/1"
}
and similarly an item like:
{
"#id": "http://api.example.com/item/1"
}
I'm more concerned about what scheme the entity of the POST request should have, rather than the URL I'm addressing the request to. Assuming I'm addressing the request to /order, would POSTing the following run afoul of HATEOAS and REST principles in any way?
{
"customer": {"#id": "http://api.example.com/customer/1"},
"item": {"#id": "http://api.example.com/item/1"}
}
To me, this seems intuitively OK. However, I can't find much or any discussion of the right way to link two independent resources with a POST. I discovered the LINK and UNLINK HTTP methods, but these seem inappropriate for a public API.
The client does not build URIs, so this is wrong unless these resource identifiers or at least their template came from the service. It is okay to use the id numbers instead of the URIs until you describe this in the response which contains the POST link.
An example from the hydra documentation:
{
"#context": "http://www.w3.org/ns/hydra/context.jsonld",
"#id": "http://api.example.com/doc/#comments",
"#type": "Link",
"title": "Comments",
"description": "A link to comments with an operation to create a new comment.",
"supportedOperation": [
{
"#type": "CreateResourceOperation",
"title": "Creates a new comment",
"method": "POST",
"expects": "http://api.example.com/doc/#Comment",
"returns": "http://api.example.com/doc/#Comment",
"possibleStatus": [
... Statuses that should be expected and handled properly ...
]
}
]
}
The "http://api.example.com/doc/#Comment" contains the property descriptions.
{
"#context": "http://www.w3.org/ns/hydra/context.jsonld",
"#id": "http://api.example.com/doc/#Comment",
"#type": "Class",
"title": "The name of the class",
"description": "A short description of the class.",
"supportedProperty": [
... Properties known to be supported by the class ...
{
"#type": "SupportedProperty",
"property": "#property", // The property
"required": true, // Is the property required in a request to be valid?
"readable": false, // Can the client retrieve the property's value?
"writeable": true // Can the client change the property's value?
}
]
}
A supported property can have an rdfs:range, which describes the value constraints. This is not yet (2015.10.22.) added to the hydra vocab as far as I can tell, but I don't have time to follow the project. I think you still can use the rdfs:range instead of waiting for a hydra range.
So in your case you could add an item property with a range of http://api.example.com/doc/#Item and so on. I assume you could add the links of the alternatives, something like http://api.example.com/items/, so you could generate a select input box. Be aware that this technology is not stable yet.
So you can send a simple JSON as POST body {item: {id:1}, customer: {id:1}} or something like that, which you generate based on the POST link. The RDF is for the client not for the server. The server can understand the data structure it requires, it does not need RDF. You don't need a dictionary to understand yourself...
I'm looking for some direction in regards to the URI design for a RESTful API. I'm going to have several nested linked resources and have currently designed my URI's similar to this post: Hierarchical RESTful URL design
The following example isn't what I'm building but I think illustrates my situation well. (Assume that a show can only belong to one network).
/networks [GET,POST]
/networks/{network_id} [GET,PUT]
/networks/{network_id}/shows [GET,POST]
/networks/{network_id}/shows/{show_id} [GET,PUT]
/networks/{network_id}/shows/{show_id}/episodes [GET,POST]
/networks/{network_id}/shows/{show_id}/episodes/{episode_id} [GET,PUT]
My situation will go two more levels further with associations but all the associations are one to many. I'm considering switching it to something similar to this:
/networks [GET,POST]
/networks/{network_id} [GET,PUT]
/networks/{network_id}/shows [GET,POST]
/shows [GET]
/shows/{id} [GET,PUT]
/shows/{id}/episodes [GET,POST]
/episodes [GET]
/episodes/{id} [GET,PUT]
My questions are:
Is the second example a valid REST design?
Should I consider implementing both paths?
The second example looks fine to me. The URLs are descriptive of the resources and the correct HTTP verbs are being used.
It is perfectly fine to have multiple URLs pointing to the same resource, if that makes sense. But more importantly, make sure the resources contain <link /> elements that connect shows to networks, episodes to shows, etc.
The real question here: does your second example fulfill the URI standard? The URI standard states, that the path contains the hierarchical part and the query contains the non-hierarchical part, but afaik. it does not tell anything about how to design the URI structure in your situation. The REST uniform interface constraints has a HATEOAS section, which means that you should send back links in your situation, which point to the upper and lower level resources. You should annotate these links with metadata, which can be processed by the client, so it will know what a link is about. So in practice the URI structure does not really matter...
GET /shows/123
{
"label": "The actual show",
"_embedded": {
"episodes": [
{
"label": "The first episode of the actual show",
"_embedded": {
"associations": [
//...
]
},
"_links": {
"self": {
"label": "Link to the first episode of the actual show",
"href": "/episodes/12345"
},
"first": {
"href": "/episodes/12345"
},
"duplicate": {
"href": "/networks/3/shows/123/episodes/12345"
},
"up": {
"label": "Link to the actual show",
"href": "/shows/123"
},
"next": {
"label": "Link to the next episode of the actual show"
"href": "/episodes/12346"
},
"previous": null,
"last": {
"href": "/episodes/12350"
}
}
}//,
//...
]
},
"_links": {
"self": {
"label": "Link to the actual show",
"href": "/shows/123"
},
"duplicate": {
"href": "/networks/3/shows/123"
},
"up": {
"label": "Link to the actual network",
"href": "/networks/3"
},
"collection": {
"label": "Link to the network tree",
"href": "/networks"
},
"next": {
"label": "Link to the next show in the actual network",
"href": "/shows/124"
},
"previous": {
"label": "Link to the previous show in the actual network",
"href": "/shows/122"
}
}
}
Now this is just something very beta in HAL+JSON with IANA link relations, but you can use JSON-LD with an RDF vocabulary (e.g. schema.org+hydra) as well. This example is just about the hierarchy (up, first, next, previous, last, collection, item, etc...), but you should add more metadata e.g. which link points to a network, which to a show, and which to an episode, etc... So your clients will know from the metadata what the content is about, and for example they can use the links to navigate automatically. This is how REST works. So the URI structure does not really matters by the client. (You can use compact URIs and URI templates as well if you want to make your response more compact.)
A URI is "any information that can be given a name"
Your question is a domain related question, and can only really be answered by someone who knows about the resources with which you are naming with a URI.
The question that comes to mind while trying to guess about your domain, is does a "show" really depend on a "network"?
What is a network in your domain? and what is the relationship between a show and a network? Is it simply someone who has aired the show? or is it more to do with production information?
I believe your example 2 is a much better fit.
Considering you have one-to-many relationships in following hierarchy:
network --> shows --> episodes
I think the second design does not provide sufficient information to the Server side to process your request. For example if you have following data:
Network id show_id episode_id
1 1 1
1 2 1
1 1 2
The first design which is verbose will provide sufficient information in HTTP request to fetch data: /networks/1/shows/1/episodes/1
The second design on the contrary will have:
/episodes/1
In the second design there is no way for server side to know if you meant row1 or row 2 from your data
To answer your question:
IMHO 2nd design may not be a valid REST design for your example. A
workaround may be to pass query parameters
I think design 1 is self sufficient
UPDATE: Please ignore my answer above
2nd design is a valid REST design for your example
Only having design 2 should also suffice
Additionally:
/networks
/networks/{id}
/shows
/shows/{id}
/episodes
/episodes/{id}
should be sufficient number of REST URLs
or in other words the following URLs would be redundant:
/networks/{network_id} [GET,PUT]
/networks/{network_id}/shows [GET,POST]
/shows/{id}/episodes [GET,POST]
I think we should keep REST API URL as simple as we can.
e.g. https://www.yoursite.com/api/xml/1.0/
Here I'm taking example of XML API for version 1.0. Please remember to use versions of the API for future updates.
You can check the method which is requested by the client.
e.g. tag
<method>getEpisodes</method>
I think the second option is Ok, but if you wanted to validate relationships, I would consider the first option. For example, when you get an episode with a different network, it can mean that the episode was modificated before your request so maybe you need to response with a 422, the same for the others services. With this, you can be sure that the entity you want to work is involving its parent.
PD: Sorry for my English.