Building a HATEOAS REST API

Good morning/afternoon folks.

I am trying to implement HATEOAS in JSON:API.

My understanding is that HATEOAS is a vital part of a REST API, being the main (only, for some people) way to deal with states and state transitions.

If this is indeed the case, I feel it’s something that should be supported out-of-the-box by JSON:API, or with specific examples that would make an implementer’s life a bit easier. Unfortunately I haven’t found anything relevant in the documentation, so it seems I will need to improvise.

OK, griping over. :slight_smile: So this is what I’ve come up with after quite a few hours of reading up on both HATEOAS and JSON:API.

My use case is very specific, so I’ll just generalise this to an example containing an “article” resource, which can transition to one of “Review” or “Reject” states instead.

The initial GET request is:
GET /article/123145

to which the API responds with 200 OK, with the following body:

{
 data: {
   type: "article",
   id: "123145", 
   attributes: {
     ....
   },
   relationships: {
     ....
   }
 }
 links: {
   related: {
     rel: "status", 
     href: "/publication/123145/status",
     params: {
       type: ["Review", "Reject"],
       method: "POST",
       expectedAttributes: ["ownerId"]
     }
   }
 }
}

So what I have done is, use the links top-level property of the response, and more specifically its related property, to provide a list of the available next states for the resource, along with some necessary (or not) parameters.

How to store the status is one question, but since the status “resource” will contain more info, such as startDate, endDate, owner etc. , I am leaning towards making it a complex attribute.

In the above example I am returning a related link object, which contains:

  • a rel value of status (which is RFC8288 compliant, as required)
  • a href value of the URL that should be used to manipulate the resource’s state
  • a params object, which provides information on how to use the above URL.

The logic behind this, is to enable the client to make a JSON:API request to the URL specified in href, in order to effect the state transition.

In the params object, I’m providing:

  • the method of the request (POST in this example),
  • the type (a required property in a JSON:API request), which contains an array of the allowed next states,
  • and the expectedAttributes property, an array containing additional parameters for the state transition.

The client, in order to set the article’s status to Review, will be expected to send a request like below:

POST article/123145/status

{
  data: {
    "type": "Review",
    "attributes": {
      ownerId: { < Some reference ID, like 5e4dba36a8148d06045b4b2a > }
    }
  }
}

To which the API will respond with 200 OK.

My questions are the following:

  • Is the above approach reasonable / compliant? Do you see any obvious flaws with it?
  • How can the API convey to the client the data types expected for the expectedAttributes ? Could I possibly leverage the describedby property of the parent object somehow? I appreciate that a “true” REST API should not rely on any OOB communication, so how can I contain all necessary information in the params object?

That’s it. I would greatly appreciate any feedback on insights that you folks might share with me, as I’ve fried my brain enough over the last few days! :smile:

Cheers,
Spyros

You may be interested in JSON hyper-schema which has already done a bunch of work in the direction you’re heading.

Being familiar with hyper-schema and a few other hypermedia efforts, the immediate flaw I see in the examples you’ve given is that the client has no way to work out what ownerId should be. You still need to encode some domain knowledge in the client so that when it sees the parameter name ownerId, it knows what sort of thing to pass (or what sort of input to display to the user).

How can the API convey to the client the data types expected for the expectedAttributes ? Could I possibly leverage the describedby property of the parent object somehow? I appreciate that a “true” REST API should not rely on any OOB communication, so how can I contain all necessary information in the params object?

I don’t think a describedby link is “OOB communication” if the client understands how to make use of it, any more than a related link is! As I understand it, “OOB” would typically refer to e.g. human-readable documentation rather than machine-readable data. It doesn’t all have to be in the same place.

HATEOAS, at the extreme end, leads you towards “thick servers” and “thin clients” - thin in the sense that they have very little domain knowledge, all they know is how to traverse hypermedia relations. When you think about it, this is exactly the situation web browsers are in. They have no idea what a “blog” is, or a “social media network”, or a “web comic” or a “store”. All the web browser knows is how to display HTML and CSS and follow links. (Let’s ignore JavaScript haha.)

So as you progress down that road, you start to need to find ways to “do more” in your hypermedia description so that the client can “do less” and pass more of that on to the user. But clients for this, aside from browsers, don’t seem to be mature yet.

Anyway that’s my little rant about HATEOAS, I’m not sure if it was entirely useful but there you go. Always consider the needs of the clients who are likely to use your API :).

1 Like