Here’s a summary of a few interesting technical details from a very good presentation on designing REST APIs by Les Hazlewood. Many interesting, elegant and not so obvious solutions here.
- Keep resources coarse-grained, especially if you’re designing a public API and you don’t know the use cases.
- Resources are either collections or single instances. They’re always an object, a noun. Don’t mix the URLs with behavior. /getAccount and /createDirectory can easily explode to /getAllAccounts, /searchAccounts, /createLdapDirectory…
- Collection is at /accounts, instance at /accounts/a1b2c3. Create, read, update and delete with GET/POST/PUT/PATCH/DELETE… Everyone knows, but anyway.
- Media types:
- application/json – regular JSON.
- application/foo+json – JSON of type foo. In my understanding, roughly corresponding to XML schema or DTD.
- application/foo+json;application – JSON of type foo, where the response type (entity format?) is application.
- application/json, text/plain – JSON prefered, text acceptable.
- Versioning:
- https://api.foo.com/v1
- Media-Type application/json+foo;application&v=1
- HREF:
- Use instead of IDs
- Sample use:
GET /accounts/a11b223 200 OK { "href": "https://api.foo.com/v1/accounts/a11b223", "givenName": "Tony", "surname": "Stark", "directory": { // Instance reference "href": "https://api.foo.com/v1/directories/ffb554" }, "groups": { // Collection reference "href": "https://api.foo.com/v1/accounts/a11b223/groups" } }
- Reference (link) expansion:
GET /accounts/a11b223?expand=directory { "href": "https://api.foo.com/v1/accounts/a11b223", "givenName": "Tony", "surname": "Stark", "directory": { "href": "https://api.foo.com/v1/directories/ffb554", "name": "Avengers", "creationDate": "2013-08-08T14:55:12Z" } }
- Partial representations:
GET /accounts/a11b223?fields=givenName,surname,directory(name)
- Pagination:
GET .../applications?offset=50&limit=25 { "href": ".../applications", "offset": 50, "limit": 25, "first": { "href": ".../applications?offset=0" }, "prev": { "href": ".../applications?offset=25" }, "items": [ {"href": "..."}, {"href": "..."} ]
- Many-to-many – represent as another, special resource, e.g. /groupMembership. Not so obvious potential benefits: If you make it a resource, it’s easy to reference from either end of the association. If you want, you can easily add data to the association (e.g. creation date, responsible user, whatever).
- Error handling – credit to Twilio.
POST /directories 409 Conflict { "status": 409, "code": 40924, // because HTTP has too few codes "property": "name", // Ready-to-use user-friendly message: "message": "A directory named Avengers already exists", // Dev-friendly message, can be stacktrace etc. "developerMessage": "A directory named Avengers already exists. If you have a stale local cache, please expire it now.", "moreInfo": "http://foo.com/docs/api/errors/40924" }
- Security – many interesting points here, but one particularly valuable. Authorize on content, not URL. URLs can change and may diverge from security configuration…
It’s too bad that the presenter doesn’t really leave the happy CRUD path. I’d really like to hear his recommendation or examples on more action-oriented use cases (validate or reset user password, approve order, what have we).
The entire presentation is a bit longer than that. I talks quite a lot about why you would want to use REST and JSON, as well as security, caching and other issues.
Of course the presenter doesn’t. REST is about resources, not operations, and in principle every operation-oriented scenario lends itself better to a RPC-ish approach (for web apps, JSON-RPC rulz, IMO, but there’s also XML-RPC or SOAP for the XML-happy).
Although, if your non-CRUD ops lend themselves to the mechanism, you can use http PATCH requests for them, and still stay RESTful.
Well, there is HATEOAS, but that’s effectively using JSON or XML instead of HTML for the same workflows as you would see on the web. So you’d go to your account, at /users/12345 and it might show you an HTML page with a link to change or reset your password. That link might require a second factor of authentication. The same process would apply through HATEOAS — you are shown a link, as a JSON parameter, to “Reset Password”. Such a link could then prompt you to verify with that second factor via POST, given HTML, what have you. The trick is making your workflows straight-forward enough that they can be planned for either because you take a standard approach to presenting options through the API for users (clients or humans) or because you’ve excessively documented what to expect in each custom interaction.
It’s … a lot of work, whether you pick a RESTful approach or not, simply because we don’t yet have good enough tools for really advanced scenarios. Nobody ever really thinks about the challenges of the semantic web any longer, but when you move the web to JSON and XML, you end up fighting the same battles as before, just on an individual API-by-API basis.