Platinum Partner
java,enterprise-integration,api,rest,tips and tricks

"Beautiful REST + JSON APIs" by Les Hazlewood

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.

Published at DZone with permission of {{ articles[0].authors[0].realName }}, DZone MVB. (source)

Opinions expressed by DZone contributors are their own.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}