I'm interested in using the HATEOAS principle of REST to reduce business logic in a SPA application. In a React-specific context, I'd like to know if there are challenges that make this impractical and, if not, what is a good strategy to follow?
Conceptual examples of using HATEOAS to remove business logic from the UI:
Delegating valid bank account actions to the REST service
Delegating role-based access control to the REST service
I've only found one link that suggests React/Flux is not compatible with a HATEOAS strategy, and no meaningful discussion elsewhere. Is it really not feasible in a React/Flux app? That SO post didn't get enough attention. Does anyone have a favorite or recommended approach for achieving success (with or without Flux or Redux)?
Someone gave a fairly detailed example of leveraging HATEOAS in the context of Angular. I'm looking for something similar for React.
Personally, I'm picturing the rel tag in hypermedia links controlling which JSX components are rendered (conditional JSX). Is that naive for a real-world React app? Perhaps conditionally rendered React components are too coarse-grained to be used this way?
I am assuming that hypermedia links are provided by a HAL implementation, or otherwise conform to the ATOM feed convention (RFC4287).
100% HATEOAS IS compatible with React & Flux, HATEOAS is compatible with Angular, HATEOAS is compatible with JQuery and even vanilla JS.
HATEOAS doesn't not impose any technical or implementation requirements on a consuming client.
HATEOAS is in fact simply a concept to which you can design your API (you can use one of several standards though like HAL)
Basically if you can call an API then you can implement a HATEOAS client.
So how to get there:
Step 1, how would you normally do an API call in React? Do it the same way.
Step 2, interrogate response.
Step 3, based on response, respond in the UI appropriately.
For example given a call to the order api /orders, I get the following response:
{
"_links": {
"self": { "href": "/orders" },
"next": { "href": "/orders?page=2" }
}
}
From this I can infer that next is a valid relation, and that if I go to that href I would in fact receive a second page of orders, so in this case in the UI show the next button.
However if I had received the following response:
{
"_links": {
"self": { "href": "/orders" },
}
}
Then I could infer that next is not a valid relation, and in my UI I should disabled or not display a next button.
There is no magic, it is simply a change in thinking, a new paradigm.
Before I spout off my most likely wrong/irrelevant answer, I just want to let you know that I just now read up on what HATEOAS is. That warning there, from what I've briefly read, HATEOAS seems to be mostly about the API telling you how to navigate through itself by providing you a link back to the resources that are relevant to the resource you had just requested.
That being the case, I don't see a reason why you can't implement dynamic url ajax calls in your actions that will alter your SPA's application state (i.e. Redux) based on what has been provided to you, however, you'll still need to have something to represent the state in a visual manner for all parts of your application. Here's a crude semi-pseudo and not very well thought-out representation of what I mean based loosely on your bank account example:
// our component file
import React from 'react'
import { makeAWithdrawl } from './actions'
export default React.createClass({
handleClick: function(e) {
e.preventDefault()
makeAWithdrawl(this.props.account.withdraw.href)
},
render: function () {
<div className="account">
<p className="account_number">{this.props.account.accountNumber}</p>
<p className="balance">{this.props.account.balance}</p>
<p><a href={this.props.account.deposit.href}>Deposit</a></p>
{this.props.account.withdraw ? <p><a onClick={this.handleClick}>Withdraw</a></p> : ''}
</div>
}
})
// our actions file
import store from 'store' // our redux store
import axios from 'axios' // an ajax library
export function makeAWithdrawl(url) {
return axios.post(url).then(function(resp){
store.dispatch({
type: 'MAKE_WITHDRAWL',
action: resp.data
}) // do your reducer stuff
})
}
Your application still knows what it's doing in the SPA, however, this will allow the API to direct you where to call to for whatever action needs to be performed. Hope it helps.
Related
Link to the HATEOAS This is the link to the Hateoas article (snapshot below) where the identifiers of the resource is part of the URL i.e. 12345. Here the API response has the final API relative URL i.e. /accounts/12345/deposit and the client just needs to hit it.
Link to the Github Users API This is the link to the Github API (snapshot below) where there are lots of placeholders for identifiers. How will clients modify these URLSs and add a value in these placeholders? For example, {/gist_id}, {/other_user}.
Isn't passing the URL with id value instead of placeholder better? Why and when to rely on different clients to add values in these placeholders?
Hypertext as the engine of application state (HATEOAS) is a bit more than just the usage of links. In essence it enforces the interaction model that is used on the Web for two decades quite successfully. On the web a server usually "teaches" clients (browsers) to achieve something via the help of link relations, that can be used to automatically download related resources or give a hint on the reference resource, and Web forms, that define the syntax and semantics of each of the respective supported (input) elements, i.e. a text field, an option element to select one or multiple choices, a drop down or even a slider widget. Based on the affordance of each of the elements a client knows i.e. that a button wants to be clicked or pressed while a text fields wants some user input and stuff or a link annotated with the prefetch link relation name may be downloaded automatically once the current page finished loading as a client might invoke it next or a preload link relation might instruct a user agent to load the referenced resource early in the current page loading process.
The form not only teaches a client about the supported fields a resource has but also about the target URI to send the request to, the HTTP method to use wile sending the request as well as the media-type, which in the case of Web forms is usually implicitly set to application/x-www-form-urlencoded.
In an ideal world a client just uses the information given by the server. Unfortunately, the world isn't perfect and over time people have come up with plenty of other solutions. Among one of them is URI templating that basically allows clients to use a basic URI and fill out certain placeholders with concrete values. As making use of templating requires some knowledge of the URIs intention or the parameters you need to pass, such capabilities make only sense as part of media-type support.
Plain JSON (application/json) has by default no support for URIs whatsoever and as such a user agent receiving a plain JSON payload might not be able to automatically replace a template URI with a concrete one out of the box. JSON Hyper-Schema (application/schema+json) attempts to add link and URI template support to plain JSON payloads. A user client though needs to be hinted with the appropriate media-type in order to automatically resolve the full URI. As such, the user agent also has to support that respective media type otherwise it won't be able to process the document (resolve the template URI to a real URI) successfully.
JSON Hypertext Application Language a.k.a HAL JSON also supports URI templates for links. application/collection+json does support two kinds of templates - query templates and objects-template. The primer one is similar to a URI template by allowing to append certain query parameters to the target URI upon sending the request while the latter one allows to define a whole object that contains all the input elements used to add or edit an item within the collection. JSON-LD does not really support URI templating AFAIK though it uses the concept of a so called context where certain terms can be used to abbreviate URIs. As such something like name can be used within the context for a URI like http://schema.org/name.
As you can hopefully see, the support for URI templating depends on the media-type used for exchanging data. In the case of the outlined github example GET /users/:username this more or less resembles a typical Web API documentation, similar as it is done in a Swagger API documentation, that unfortunately has hardly anything to do with HATEOAS.
For your top example (banking), you should absolutely include the complete URL, with account numbers (IDs), so that the client does not need to translate/substitute anything. This is the most common scenario with HATEOAS. However, GitHub does have those "placeholders" for endpoints that could contain multiple values. You can't include the "following_url" for every user in the response, it's not practical. So you have to determine the "other_user" value another way and make the substitution. Personally, I haven't even had this use case with any of my applications and all of my HATEOAS URLs resemble you first example (though I prefer full URLs not relative). Unless you have specific cases like GitHub does, it's not necessary to use any of these placeholders. Even GitHub only uses that where they could be multiple values. For fixed value URLs, they have the username (like your account number) in the URL ("octocat").
According to me we should not give the direct url in the body
We should always parameterized the api and get details form there.
In simple case if Id of data change than every time data need to update for detail url.
Else if it’s dynamic you will never face this issue.
And this also come under best practices.
We have listing and details page for mobile, desktop, android and ios?
We have two apis- one for listing and other for details of those listings. So one api is assosiated with other.
listing api looks like:
/api/books/?price=100-300
details api looks like:
/api/book/{bookId}
listing api in response sends back details api link for each listing:
For example:
Request:
/api/books/?price=100-300
will have Response:
{
"books":[
{
"id": 1,
"price": 120,
"pages": 400,
"detailsUrl": "/api/book/{bookId}"
}
]
}
The problem is, should I send detailsUrl with each listing or let all the clients create this? Since they have bookId, they can create it.
What is considered to be best practise considering the url params in details api url may get added in future, api may be versioned and all other possibilities for widely used apis?
I'd put my money on creating the link.
There is a perfect explanation why putting id is not enough
DO NOT include just an entity's ID (such as 12) in a response, because
that way you're forcing clients to put together resource URIs
themselves. In order to do that, they would need to have prior
knowledge of what URIs there are, and you're losing control over the
URI space on the server side.
I recommend to read the entire, linked text (approved answer), it's very detailed and got nice resources. As the author mentioned, there is a popular convention how to make connections between resources - HAL. Consider this instead of detailsUrl.
Even if a link to the resource is obvious right now and you decided to put id alone, in other resources after some time it won't be so simple and you will be forced to specify full URL. Then your API won't be consistent. Experience has taught me it's always easier and better for maintenance to send back full link.
What I'm trying to do is described in great detail here:
Call a Server-side Method on a Resource in a RESTful Way
I have Ember Data's RESTAdapter working with my API, but now I want to give Ember.js a way to kick off various server-side actions using custom routes, such as /docs/1/share or /docs/1/activate. The former would possibly modify the record but the latter would not.
What's the best way to do this?
TIA!
Ember has jQuery baked in. In your controller:
actions: {
activate: function() {
var docId= this.get('id'), self= this;
Ember.$.ajax({
url: '/docs/%#/activate'.fmt(docId),
// your other details...
}).then(function(resolve) {
self.set('name', resolve.doc.name);
// process the result...
});
}
}
You can also use ic-ajax which is a nice wrapper around jQuery.ajax, you can see an example here using ix-ajax.
I'm building a web app using Ember 1.5 and I use Ember Model 0.0.11 to link the app to an API. I'm currently having trouble getting Ember Model to use nested API endpoints.
For instance, my app has a User model, which has a hasMany relationship with a Post model. Now, when I want to load the posts for a certain user, I'd like the Post.findQuery('user_id', {user_id}); method to access the GET /users/{user_id}/posts endpoint. Instead, it seems that the Ember Model solution prefers to send the query as a parameter to the GET /posts endpoint.
Ember Model's RESTAdapter does allow for easy customization, but before I start coding a completely custom RESTAdapter I'd love to know if someone has already done this and how they solved the problem.
The solution for us to handle nested API routes was to have a links hash returned with the requested payload. If you can change the API output, this is the way to go for now. For example, when requesting a workspace, the returned JSON looks like this (excuse the escaped characters):
{
"data":
{
"object":"workspace",
"id":"wrk_krVZWGaJ",
"organization_name":"Legros, Klein and Boehm",
"workspace_name":"Legros, Klein and Boehm",
"workspace_path":"legros, klein and boehm",
"status":true,
"credit_production":7,
"credit_revision":16,
"links":{
"projects":"\/v1\/workspaces\/wrk_krVZWGaJ\/projects",
"productions":"\/v1\/workspaces\/wrk_krVZWGaJ\/productions",
"subscription":"\/v1\/workspaces\/wrk_krVZWGaJ\/subscription",
"assets":"\/v1\/workspaces\/wrk_krVZWGaJ\/assets",
"descriptions":"\/v1\/workspaces\/wrk_krVZWGaJ\/descriptions",
"roles":"\/v1\/workspaces\/wrk_krVZWGaJ\/roles",
"registrations":"\/v1\/workspaces\/wrk_krVZWGaJ\/registrations",
"users":"\/v1\/workspaces\/wrk_krVZWGaJ\/users"
}
}
}
The model defines the links as async hasMany relationships, i.e. DS.attr('projects', {async: true}). When the workspace is loaded from the store, the linked projects aren't included. When you use {{#each workspace.projects}} or workspace.get('projects') anywhere else, ember-data will make a GET request to /v1/workspaces/wrk_krVZWGaJ/projects/, rather than the default route of /projects/. Neato, eh?
More details on the RESTAdapter findHasMany method.
I have a drop-down in my application for which i populate the data using an Ajax call as shown below. This works fine and my Web API URL is "http://example.com/Service.svc/json/getairports"
$(function () {
$.getJSON("http://example.com/Service.svc/json/getairports", {}, function (data) {
$("#airport-departure-modal, #destination-airport-modal").html("")
$.each(data, function (index, element) {
$("#airport-departure-modal, #destination-airport-modal").append("<option id=" + element["AirportCode"] + ">" + element["AirportName"] + "</option>");
});
});
});
But my worry is security since anyone can view this URL using view source or developer tools. So i wanted to bring in a token and pass it to the service like "http://example.com/Service.svc/json/getairports?token=SECUREKEY" but i wonder how this can solve the problem since secure key also visible in the view source. So my question is how can i keep the secure key invisible in the client side or dynamically passed only when the ajax call is initiated?
Just for information, i will be using HTTPS in production so that sniffing over the wire is taken care. Also not that, this service is going to be used only within the same application though Web API service might be hosted on a separate node.
Kindly advise if there might be some alternative but simple solution for the above scenario. i am aware of other advanced mechanisms such as OAuth,HMAC, Amazon S3, etc. But i just want to have a simple solution.
What exactly are you trying to solve? Do you want to prevent a user from calling your API programmatically? If your browser can get something, so can a user with the power of view-source - so that's a fruitless effort. Really though, it sounds like you want to prevent CSRF.
This answer should be helpful.