Update 5/12/2016: Stormpath now secures authentication to your API- without code!
Also, we have some awesome new resources for API developers building user management:
- Easy API Key Management with Node.js
- PHP API Authentication is a PITA!
- The Fundamentals of REST API Design (a presentation by Stormpath CTO Les Hazlewood)
Also, we’d love to have you try out JJWT, a Java library providing end-to-end JWT creation and verification, developed by our very own Les Hazlewood. Forever free and open-source (Apache License, Version 2.0), JJWT is simple to use and understand.
While the specifications for both XML (via XLink) and HTML (via anchor tags, the “a” element) have hypertext references built into their respective specifications, there is no such JSON-specific specification for linking. This means there are many different ways to add hypertext to your REST + JSON API. Some examples include JSON API, Collection+JSON, HAL, and Siren.
But Stormpath has our own approach, which is particularly simple and elegant while enabling a very powerful feature common to REST APIs. We will cover it in this post in case you want to use it in your own API.
Why use Linking
Linking can help with discoverability and use-case scalability. Presenting the fully qualified URL for every resource aids discoverability by allowing for new resources to be consumed by just embedding a new reference (link). This works well for JSON because JSON is schema-less; adding new fields will not break functionality and will maintain backwards compatibility.
Fortunately, if a resource’s endpoint changes, the new URL will be returned and consumed as though nothing happened, which aids scalability. Additionally, providers should support 302 redirects for deprecated URLs to ensure clients with older URLs still work. Since virtually every type of web client can read a URL, developers do not need to use any additional libraries to create or parse the URLs.
Pagination is a great example of when to use linking when returning resource collections. Developers working with your API might not know how pagination works or know how to build the URL for the next set of results. Instead, you can provide them links that automatically support pagination. Here is an example of a paginated collection of users where the pages are more easily discoverable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
HTTP/1.1 200 OK { "href" : "https://api.mycompany.com/v1/users?offset=50&limit=50" "offset": 50, "limit": 50, “first”: { “href”: "https://api.mycompany.com/v1/users" }, “prev”: { “href”: "https://api.mycompany.com/v1/users" }, “next”: { “href”: "https://api.mycompany.com/v1/users?offset=100&limit=50" }, “last”: { “href”: "https://api.mycompany.com/v1/users?offset=50&limit=50" }, "items": [ { … user 51 name/value pairs … }, …, { … user 100 name/value pairs … } } } |
In this response, you can see the items
property contains all the resource objects from the current page. You can access the current offset
and limit
so you can construct the next or previous page link if you want to. But if you don’t know how to use those properties, the first, previous, next, and last page links are available for you to use immediately. This makes it simple for a client to page through your resource collection results easily. In other words, the pages are more easily discoverable because you provided links for them already.
References (links) in JSON
As we mentioned, there is no JSON standard for hypertext references, so we have to define our own technique. We have a really simple and concise implementation. Here is an example of how an account resource is returned by the Stormpath API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 { "href" : "https://api.stormpath.com/v1/accounts/cJoiwcorTTmkDDBsf02AbA", "username" : "jlpicard", "givenName" : "Jean-Luc", "middleName" : "", "surname" : "Picard", "status" : "enabled", "directory" : { "href" : "https://api.stormpath.com/v1/directories/WpM9nyZ2TbaEzfbRvLk9KA" }, … } |
This Account resource and every other JSON resource will always have an href
property, which is the fully qualified canonical URL where that resource resides. Whenever you see an href
property, you know you can access that resource by executing a GET
request to the resource’s URL.
This holds true for resource references too. Notice the directory
property in the above example. The directory
property is a complex object that itself has an href
attribute as well. Because the directory object has an href
property, we know it is a resource itself, and the fact that an href is available means we have a direct reference or ‘link’ to that resource.
Link Relations
For those familiar with XLink and HTML anchors, you’ll recall that there is a ‘rel’ (short for ‘relation’) attribute that can be specified, indicating the relation – or purpose – of the reference:
<a href=”https://host/help.html” rel=”help”/>
Here the help
relation attribute value indicates the referenced URL can be used to access context-sensitive help documentation.
But things can be even more succinct in JSON. What is interesting about the directory
property above is that the JSON property name itself already indicates the purpose of the reference! The JSON property name can be an implicit/automatic ‘relation’ definition.
Alternatives
As you see above, we use the name of the property to indicate the relationship of the URL. For contrast, you can see how this differs from a HAL example below, which shows all of the links defined in a separate array of objects.
Although this provides a centralized place to store all the relationships, the JSON response becomes more verbose and requires you to context switch as you review the document. Instead of just having a simple object with name-value pairs, some of which are references (as Stormpath has), you now have an array of objects, each object having a “rel” and “link” tag.
1 2 3 4 5 6 7 8 9 |
{ … “links”: [ { “rel” : “directory”, “link” : “https://api.stormpath.com/v1/directories/WpM9nyZ2TbaEzfbRvLk9KA"}, { “rel” : “tenant”, “link” : “https://api.stormpath.com/v1/tenants/wGbGaSNuTUix9"} ], … } |
Why do we prefer our approach over the HAL technique? The href technique becomes particularly valuable when it comes to Resource Expansion (aka ‘link’ expansion).
Resource Expansion
Resource expansion can be compared to eager loading in the database realm. Both offer ways to load a target resource as well as other resources related to it. REST API callers should be encouraged to use resource expansion when they know they are going to need the extra information. This will reduce the amount of API calls as well as the amount of traffic to and from your API server. Resource expansion might be avoided, however, when you want to reduce the amount of information returned or if you are unsure if you will need the additional resource object.
Let’s revisit the previous account resource with the directory link. Let’s say we support an expand
query parameter that tells the server, “not only do I want the account, but I also want the account’s parent directory expanded in line, in a single response”. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
GET /v1/accounts/ZugcG3JHQFOTKGEXAMPLE?expand=directory Response { "href": "https://api.stormpath.com/v1/accounts/ZugcG3JHQFOTKGEXAMPLE", "username": "lonestarr", "fullName": "Lonestarr Schwartz", "givenName": "Lonestarr", "middleName": "", "surname": "Schwartz", "status": "ENABLED", "emailVerificationToken": null "directory": { "href": "https://api.stormpath.com/v1/directories/S2HZc7gXTumVYEXAMPLE", "name": "Spaceballs", "description": "", "status": "ENABLED", "accounts": { "href":"https://api.stormpath.com/v1/directories/S2HZc7gXTumVYEXAMPLE/accounts" }, "groups": { "href":"https://api.stormpath.com/v1/directories/S2HZc7gXTumVYEXAMPLE/groups" }, "tenant":{ "href":"https://api.stormpath.com/v1/tenants/wGbGaSNuTUix9EXAMPLE" } }, "tenant": { "href": "https://api.stormpath.com/v1/tenants/wGbGaSNuTUix9EXAMPLE" }, } |
See how the directory has been expanded and fully included in the response payload?
Why is this really nice? Because REST clients don’t have to implement special processing logic to check different properties for expanded resources. REST clients only have to worry about one thing when encountering a nested object property: Does the nested object have only an href
property?
If yes, then it is clearly a link, and it can be retrieved via a GET request.
If no (other properties exist as well), then it has already been expanded in line, and no further request to the server is required.
We believe this is more intuitive and easier to understand for humans reading the JSON payload, as well as easier to process for machine/REST clients that need to figure out how to interact with resources further. In our example above, the directory
property will always be the placeholder for the directory, expanded or not. There is nowhere else in the document for a client to look to find out about links.
While the HAL approach can be a valid technique (a separate array of links), we feel it is less convenient and less readable than the in-line property technique, especially if you support resource expansion; expanding a link in the HAL links array means that the array is no longer a link array – it is an array of some links, some expanded resources. We feel this isn’t as intuitive and self-evident as using in-line properties with self-documenting relations.
Stormpath has multiple ways to reach resource expansion targets, reducing the amount of multiple calls. Here are some examples:
- Expanding Multiple References:
GET /v1/accounts/ZugcG3JHQFOTKGEXAMPLE?expand=directory,tenant
- Expanding Collection References:
GET /v1/accounts/ZugcG3JHQFOTKGEXAMPLE?expand=groups(offset:0,limit:10)
- Expansion Combinations:
GET /v1/accounts/ZugcG3JHQFOTKGEXAMPLE?expand=directory,groups(offset:0,limit:10)
Read more about Stormpath Reference Expansion in our API Documentation.