How should HATEOAS-style links be implemented for RESTful JSON collections?

37,597

Solution 1

Please don't dismiss HAL so quickly just because it looks a little bloated (in its JSON form, it's quite minimal).

HAL is to JSON what HTML is to plain text.

It adds hyperlinks. You need hyperlinks and a commonly understood representation format (such as HAL or Collection+JSON) for REST. You also need HATEOAS for REST, without HATEOAS it isn't REST! HATEOAS requires hyperlinks of course.

In your case, you are trying to build a collection resource. The IANA-registered relation for that is "item" (with reverse relation "collection"). Here is the representation in HAL for a People collection:

{
    "_links": {
        "self": { "href": "http://example.com/people" },
        "item": [
            { "href": "http://example.com/people/1", "title": "John Smith" },
            { "href": "http://example.com/people/2", "title": "Jane Smith" }
        ]
    },
    "_embedded": {
        "http://example.com/rels#person": [
            {
                "first_name": "John",
                "last_name": "Smith",
                "_links": {
                    "self": { "href": "http://example.com/people/1" },
                    "http://example.com/rels#spouse": { "href": "http://example.com/people/2" }
                }
            },
            {
                "first_name": "Jane",
                "last_name": "Smith",
                "_links": {
                    "self": { "href": "http://example.com/people/2" },
                    "http://example.com/rels#spouse": { "href": "http://example.com/people/1" }
                }
            }
        ]
    }
}

Note:

  • The primary data for this collection comes from _links.item[]. These are the items in the collection. The full (or at least some additional) data for each item is available in the _embedded array. If the client needs these additional data, it must find them by searching through _embedded[n]._links.self.href for each n. This is a design constraint of HAL. Other hypermedia representation formats have similar constraints (though perhaps going in the other direction).

  • I have added a title value for each member of the item array. This can appear between the opening and closing anchor tags if rendering to HTML, or as the text of a menu item in the client, without need for further processing of the representation by the client.

  • There are no ID parameters. All references to other resources are exposed as hyperlinks. A client should not have to "build" a URL by gluing an ID into a URL at some pre-defined place. This constitutes out-of-band information which inhibits independent changes to the client and server.

  • All of your hyperlinks should be absolute, since relative URLs may cause problems. All of your relations should be either listed on that IANA page or use a URI to define them. Ideally, that URI should be a dereferencable HTTP URL with documentation about the relation at the other end.

Solution 2

First, I don't believe API's which have endpoints that return collections (JSON arrays) are truly RESTful. However, most "REST" API's bend the rules here.

I recently developed a REST API for the NextBus XML feed called restbus that returns collections from some endpoints while using HATEOAS style hypertext links. Here is a sample of the structure I used:

{
  // ... SF-Muni resource from restbus API ...

  _links: {
    self: {
      href: "http://localhost:3535/agencies/sf-muni",
      type: "application/json",
      rel: "self",
      rt: "agency",
      title: "Transit agency 'sf-muni'."
    },
    to: [
      {
        href: "http://localhost:3535/agencies/sf-muni/routes",
        type: "application/json",
        rel: "describedby",
        rt: "route",
        title: "A collection of routes for transit agency 'sf-muni'."
      },
      {
        href: "http://localhost:3535/agencies/sf-muni/vehicles",
        type: "application/json",
        rel: "describedby",
        rt: "vehicle",
        title: "A collection of vehicles for transit agency 'sf-muni'."
      }
    ],
    from: [
      {
        href: "http://localhost:3535/agencies",
        type: "application/json",
        rel: "bookmark",
        rt: "agency",
        title: "A collection of transit agencies. This is the API root!"
      }
    ]
  }

}

It doesn't try to follow any of the popular JSON linking strategies out there (or their associated media types) like HAL et al. because they don't appear to be on the IETF Standards Track (yet). Instead the link object target attributes and link relation values meet RFC 5988 Web Linking specifications as much as possible.

You can see more details about the restbus hypertext link structure.

Solution 3

You may try to look at Restful object specification. That guys create concrete API. As I don't like the whole idea, there is a many practical solutions you can grab from them.

Share:
37,597
thomas-peter
Author by

thomas-peter

Meh!

Updated on July 02, 2021

Comments

  • thomas-peter
    thomas-peter almost 3 years

    To keep things simple and to avoid nameing collisions, I've been bundling links in my record resources like this...

    {
        id: 211,
        first_name: 'John',
        last_name: 'Lock',
        _links: [
            { rel: 'self', href: 'htttp://example.com/people/211' }
        ]
    }
    

    However, I can't work out how to implement links in collections. I have spent a long time trawling around the web for examples and other than using the not so lean HAL I'm unable to reconcile my problem.

    [
        {id:1,first_name:.....},
        {id:2,first_name:.....},
        {id:3,first_name:.....}, 
        "_links": "Cant put a key value pair here because its an-array" 
    ]
    

    Which means I have to wrap up the array in a container object.

    {
        people: [ {id:1,first_name:.....} ],
        links: [ { rel:parent, href:.... ]
    }
    

    But is is different to the singular resource so I'm going to make the record behave like the collection and wrap it up in a container....

    {
        person: {
            id: 211,
            first_name: 'John',
            last_name: 'Lock'
        },
        links:[
            { rel: 'self', href: 'htttp://example.com/people/211' }
        ] 
    }
    

    On the surface, this seems like quite a neat solution. The resulting JSON is one level deeper, but HATEOAS has been implemented, so that's all good right? Not at all. The real sting comes when I go back to the collection. Now that the single resource has been wrapped up in a container in order to be consistent with the collection, the collection must now be changed in order to reflect the changes. And this is where it gets ugly. Very ugly. Now the collection looks like this...

    {
        "people": [
            {
                "person": {
                    ....
                },
                "links" : [
                    {
                        "rel": "self",
                        "href": "http://example.com/people/1"
                    }
                ]
            },
            {
                "person": {
                    ....
                },
                "links" : [
                    {
                        "rel": "self",
                        "href": "http://example.com/people/2"
                    }
                ]
            }
        ],
        "links" : [
            {
                "rel": "self",
                "href": "http://example.com/people"
            }
        ]
    }
    

    Is there a simpler solution to implementing HATEOAS for collections? Or should I kiss HATEOAS goodbye for forcing me to over complicate the data structure?

  • Tivie
    Tivie about 10 years
    Why do you think API's which have endpoints that return collections (JSON arrays) are NOT truly RESTful? I'm interested in your opinion.
  • Trevor Robinson
    Trevor Robinson about 10 years
    HAL does have an IETF standard draft, though it's about to expire in a few days.
  • morganney
    morganney about 10 years
    @Tivie I'm probably nitpicking here, but to me REST API endpoints should return a representation for one, and only one resource. An array may return more than one resource, and kind of muddies the waters in terms of definitions. Instead of returning an array of resources, maybe create a new resource that contains the aggregate information found in the array.
  • Nicholas Shanks
    Nicholas Shanks almost 10 years
    You input is helpful, but I would argue that a collection of resources is still a resource in itself. Just as a resource (e.g. /cars/1) might have 'subresources' (/cars/1/make ⟼ "Rolls Royce") that could be fields in the parent resource or perhaps not exposed at all in the parent resource.
  • sp00m
    sp00m almost 9 years
    Side question: why are you prefixing _links and _embedded attributes with an underscore? Is it part of some naming conventions?
  • Nicholas Shanks
    Nicholas Shanks almost 9 years
    @sp00m It's part of the HAL specification, it separates data from metadata: stateless.co/hal_specification.html
  • Admin
    Admin over 8 years
    I m setting GET https://, but spring HATEOAS gives me only http://, I am using nginx/1.6.0.
  • Nicholas Shanks
    Nicholas Shanks over 8 years
    @user4567570 I don't use Spring, but I presume your routes have been configured to be insecure, and Spring is just generating URLs for the routes it knows..