Skip to content

Instantly share code, notes, and snippets.

@miyagawa
Created February 26, 2012 02:53
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save miyagawa/1912431 to your computer and use it in GitHub Desktop.
Save miyagawa/1912431 to your computer and use it in GitHub Desktop.
How to represent Links in REST

How to embed HATEOAS/Link inside JSON

HTTP Headers

For HTTP headers it's quite straightforward: draft-nottingham-http-link-header defines this.

Link: <http://example.com/>; rel="previous"; titile="Previous chapter"

Perl's LWP library automatically parses this headers, and in Ruby there's link_header gem.

Implemented in GitHub API v3.

RESTful JSON response

There's a couple of ways to embed links inside a JSON data structure. Here's the list I can come up with:

rel as a key, url as a value

{
  "id": 1234,
  "name": "Bob",
  "links": {
    "self": "/people/1234"
  }
}

Pros:

  • Simple. Easy to parse and access in client libraries

Cons:

  • Can't include attributes other than href. What if I want to include title and type for example?
  • Can't have multiple links with the same rel, although i think it is a bad practice to have multiple links with the same relationship anyway.

Facebook Graph API implements this format under connections if you request the API with ?metadata=1.

rel as a key, url as a hash

{
  "id": 1234,
  "name": "Bob",
  "links": {
    "self": { "href": "/people/1234" },
  }
}

Pros:

  • Easy to parse, yet extensible and can include other attributes

Cons:

  • Can't have multiple links with the same rel. Same as above.

GitHub API v3 implementes this format in some of the data types.

links as an array

{
  "id": 1234,
  "name": Bob,
  "links": [
    { "rel": "self", "href": "/people/1234" }
  ]
}

Pros:

  • Extensible and straightforward.

Cons:

  • A little bit annoying to parse, although it could be still one-line grep or select in most languages, and you can write a wrapper anyway.

Ruby gem roar implements this format.

@jshirley
Copy link

Have you seen Jackalope? Stevan's go at it: https://github.com/stevan/jackalope

We use it at II, it works pretty well for providing additional links, as well as providing schemas that are validated against for each endpoint.

It uses the second format (links as an array), which has served us well in the real world after some pretty heavy usage.

@steveklabnik
Copy link

You need to define a media type. Using JSON's serialization semantics is totally fine, but you can't define links inside of application/json.

For two examples of hypermedia-enabled JSON, see HAL and Collection+JSON.

@miyagawa
Copy link
Author

@jshirley yes, I tried Ruby's roar gem (mentioned in the original post) and it does basically the same thing.

@miyagawa
Copy link
Author

@steveklabnik I don't think defining a media type is mandatory. In fact one of the trends I observe is to provide one resource endpoint and let clients negotiate the media type using the Accept header.

@miyagawa
Copy link
Author

@steveklabnik Thanks for the links to HAL and Collection+JSON. Quick glance shows that HAL uses #2 format (same as github's API)

@steveklabnik
Copy link

I mean that you, as the producer of your API should be. Accept headers are how clients request a specific type they have knowledge of.

What you're doing here is defining a media type; extra semantics on top of something. application/json has no link semantics, so as soon as you say "We're serving application/json, but we're adding a 'links' element to each response", you've created a new media type, just given it no name for a client to Accept against!

This is why Fielding says A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types. Any effort spent describing what methods to use on what URIs of interest should be entirely defined within the scope of the processing rules for a media type (and, in most cases, already defined by existing media types). [Failure here implies that out-of-band information is driving interaction instead of hypertext.]

If you don't formalize and document the media type, the fact that you have a 'links' section is out-of-band knowledge, and breaks the 'self-descriptive messages' sub-constraint of the uniform interface constraint.

@miyagawa
Copy link
Author

Oh, sorry I thought you meant that you should add MIME-type as a type attribute to each of the links in the body. Yeah, I guess you should be able to represent this entire JSON format in something other than application/json like application/vnd.mycompany.blahblah or something like Collection+JSON.

@steveklabnik
Copy link

Ahhh yeah, I'm not a favor of adding type information to the link.

I would exactly make it application/vnd.mycompany.blahblah+json. :)

@steveklabnik
Copy link

... the important thing to remember is that whichever of these things you choose, it's no longer application/json any longer. Which is totally fine, and actually, better, even.

@steveklabnik
Copy link

Oh, and last thing, since I didn't actually weigh in on which format: I actually like the ROAR or GitHub ones the best. :)

@steveklabnik
Copy link

I'll just leave this here: https://gist.github.com/794419

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment