How to Take Your API From RPC to Hypermedia in 7 Steps

Continued from page 4. 

To solve this problem, we will introduce a Collection type of resource, with its own hash URI and a property members pointing to the URIs of the contained resources.

#%RAML 1.0

title: Todos Service
baseUri: http://todosapp.com/api/version_3_2
mediaType: [ application/ld+json, application/json, application/xml ]

types:
  URI: string
  Link:
    properties:
      @id: URI
  Context:
    properties:
      @vocab: URI
  User:
    properties:
      @context: Context
      @id: URI
      email: string
      name: string
      todos: Link
  Todo:
    properties:
      @context: Context
      @id: URI
      title: string
      description: string
      user: Link
  Collection:
    properties:
      @context: Context
      @id: URI
      members: Link[]

/users:
  get:
    description: Gets all the users in the service
    responses:
      200:
        body: Collection
  post:
    description: Creates a new user
    body:
      properties:
        email: string
        name: string
        password: string
    responses:
      201:
        headers:
          Location:
            type: string

/users/{id}:
  get:
    description: Information about a single user of the service
    responses:
      200:
        body: User
  delete:
    description: Deletes a user from the service

/users/{id}/todos:
  get:
    description: Lists all the Todos created by a user
    responses:
      200:
        body: Collection
  post:
    description: Creates a new Todo for a user
    body:
      properties:
        title: string
        description: string
    responses:
      201:
        headers:
          Location:
            type: string

/users/{id}/todos/{todo_id}:
  get:
    description: Information about a single Todo for a user
    responses:
      200:
        body: Todo
  delete:
    description: Deletes a Todo for a User from the service

If we now follow a link leading to a Collection resource in our data graph we will obtain the following representation:

{
  "@context": { "@vocab": "http://todosapp.com/api/vocab#" },
  "@id": "http://todosapp.com/api/users/1/todos#self",
  "members": [
    { "@id": "http://todosapp.com/api/users/1/todos/1#self" },
    { "@id": "http://todosapp.com/api/users/1/todos/2#self" }
  ]
}

And we could add that information to our data graph to obtain a more connected graph:

Another design decision we have taken is to reduce the properties in the members of the collection resource to include just the link to the member resource. That’s the only requirement from the hyper-media point of view, but that also supposes that we need to issue a new HTTP request if we want to obtain any other information about the member resource.

The opposite solution would be to embed the full member resource data graph in the collection representation, minimising in this way the number of required requests to retrieve the information but increasing the size of the request payload. Intermediate solutions, where some properties are in-lined are also possible. The designer of the API must decide what is the right granularity for her use case.

API Version 3.3: Entry Point

In version 3.2 of the API we have connected our data graph of resources introducing Collection resources. However there’s still a resource we cannot reach the collection of all the users http://todosapp.com/api/version_3_2/users#self. We still need to document that ‘end-point’ in our RAML API spec for a client capable of following hyper-links to be able to explore our API.

In fact, that’s the only URI that we need to document, since all the resources in the API include hyper-links in their representation and they are documented in the description of the resource types.

One of the goals of REST design is that a single URI should be enough for a client to consume the API. We will make that idea explicit introducing a new type of resource an EntryPoint resource type with a single resource, the URI that will be the entry-point for our API. The entry-point itself will only include a link to the collection of all users.

We will also remove all the other end-points in our RAML specification to leave only the reference to the entry-point URI.

#%RAML 1.0

title: Todos Service
baseUri: http://todosapp.com/api/version_3_3
mediaType: [ application/ld+json, application/json, application/xml ]

types:
  URI: string
  Link:
    properties:
      @id: URI
  Context:
    properties:
      @vocab: URI
  EntryPoint:
    @context: Context
    @id: URI
    users: Link
  User:
    properties:
      @context: Context
      @id: URI
      email: string
      name: string
      todos: Link
  Todo:
    properties:
      @context: Context
      @id: URI
      title: string
      description: string
      user: Link
  Collection:
    properties:
      @context: Context
      @id: URI
      members: Link[]

/:
  get:
    description: Entry point for the application
    responses:
      200:
        body: EntryPoint

An example of API with entry-point is again Github’s HTTP API where it is denominated ‘Root Endpoint’.

$ curl -X GET https://api.github.com/

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "notifications_url": "https://api.github.com/notifications",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_url": "https://api.github.com/orgs/{org}",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "team_url": "https://api.github.com/teams",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

API Version 4: Read-Write API

In the different revisions 3.X of the API we have built a quite sophisticated REST API with support for hyperlinks.

The main problem with the resulting API is that it is read-only. It would be equivalent to a version of the Web working with HTML supporting only ‘<a></a>’ tags and without support for ‘<form></form>’ elements.

To define a read-write version of the API, we need to support richer forms of hypermedia encoding control information for the whole HTTP uniform interface.

JSON-LD only provides syntax to encode hyperlinks in the JSON-LD document, but this is not a problem, since we can insert the required hypermedia controls in the data graph, encoding them in the same way we encode the data.

Antonio Garrote Principal Engineer at Mulesoft in the mornings, researching on RESTful semantic web services in the evenings. Clojure enthusiast all day long.
 

Comments

Comments(1)

alexsmith84

Great article! I'm currently only learning but i do have some ideas for future projects and i feel like your tips will help me a lot! I'm currently trying to do something with doahomework.com API