backbone.hateoas HATEOAS with Backbone

backbone.hateoas is a Javascript library which facilitates the use of HATEOAS (Hypermedia as the Engine of Application State) and the HAL (Hypertext Application Language) standard in your Backbone and Marionette applications.

To understand what is HATEAOS and what's its place in REST in general we advise you to understand the Richardson Maturity Model and most specifically the REST Level 3 - Hypermedia Controls.

Today several standards exists to model REST resources and links between them - HAL, JSON-LD, Collection+JSON, SIREN to name a few.

backbone.hateoas uses the HAL standard because its JSON format is easy to manipulate with Backbone and because we think its the most used one today.

The HAL specification is quickly described here HAL - Hypertext Application Language, the JSON version of HAL has also been published as an internet draft here : JSON Hypertext Application Language.

Getting started

Introduction

The HAL standard defines a generic resource concept, but Backbone defines 2 kinds of "resources" (the models and the collections).

So backbone.hateoas defines 2 kinds of resources :

  • The Hal.Model class defines a generic HAL resource and is very similar to the Backbone.Model class ;
  • The Hal.Collection class is a specialized HAL resource dedicated to manipulation of collection resources, this one is a little opinionated (but its design is based on REST API best practices).
Hal.Model

The Hal.Model class extends the Backbone.Model class and adds support to manipulate HAL links and HAL embedded resources.


// Instanciation of an Hal.Model object is done the same way as you're used to with a standard Backbone model
var user = new Hal.Model({
    firstName: "John",
    lastName: "Doe",
    _links: {
        avatar: {
            href: "http://localhost/api/users/1/avatar.png" 
        }
    },
    _embedded: {
        address: {
            "city" : "Paris",
            "country" : "France",
            "street" : "142 Rue de Rivoli",
            "zip" : "75001",
            "_links" : {
                "self" : {
                    "href" : "http://localhost/api/addresses/1"
                }
            }
        }
    }
});

// "Direct" properties are accessed using standard Backbone methods
user.get('firstName');
user.get('lastName');

// Getting a link, a link can be manipulated like a Backbone.Model
user.getLink('avatar').get('href');

// Getting an embedded resource, an embedded resource is a Hal.Model
user.getEmbedded('address').get('city');
user.getEmbedded('address').getLink('self').get('href');

The Hal.Model class has been designed to be very easy to manipulate, we've chosen to create getLink(rel) and getEmbedded(rel) methods to force the developpers to clearly show their intention in the code.

For more informations abount the Hal.Model class please read the chapter Hal.Model.

Hal.Collection

The Hal.Collection class extends the Backbone.Collection class by adding utility methods to navigate through an "HAL Collection". The HAL standard does not describe the concept of an HAL collection but most framwork do it ( Apigility, Willdurand Hateoas, etc.). The Hal.Collection class is configurable and can be used to work with custom pagination parameter names and links.

For more informations abount the Hal.Collection class please read the chapter Hal.Collection.

Install

The easiest way to use the library is to pull it with Bower by adding the following dependency inside your bower.json file.

{
    "dependencies": {
        "backbone.hateoas" : "~0.1"
    }
}

Global configuration

contentType

The HAL standard defines 2 new media types which are application/hal+json and application/hal+xml, it also indicates the following :

When serving HAL over HTTP, the Content-Type of the response should contain the relevant media type name.

So it defines how the server should respond but is does not define how we should send HTTP POST, PUT and PATCH requests. In fact this is something which seems to be left unspecified deliberatly.

The Hal.contentType global configuration parameter allows to configure how your models and collections should be serialized before being sent to the server (i.e it changes the behavior of the Backbone Model.toJSON() and Collection.toJSON() methods).

The current version of the library supports 2 content types : application/json and application/hal+json.


Sample with application/json

The following sample ...

// The 'Hal.contentType' parameter is an application wide parameter
// So in most cases it is only configured at one place in your project
Hal.contentType = 'application/json';

...

// Somewhere in the code of the application
var john = new Hal.Model();
john.urlMiddle = 'users';
john.set('id', 'jdoe');
john.fetch({
    success : function() {
        console.log(user.toJSON());     
    }
});

Will display ...

{
    "id": 1,
    "firstName": "John",
    "lastName" : "Doe",
    "address" : {
        "city" : "Paris",
        "country" : "France",
        "street" : "142 Rue de Rivoli",
        "zip" : "75001"
    }
}

Sample with application/hal+json

The following sample ...

// The 'Hal.contentType' parameter is an application wide parameter
// So in most cases it is only configured at one place in your project
Hal.contentType = 'application/hal+json';

...

// Somewhere in the code of the application
var john = new Hal.Model();
john.urlMiddle = 'users';
john.set('id', 'jdoe');
john.fetch({
    success : function() {
        console.log(user.toJSON());     
    }
});

Will display ...

{
    "id": 1,
    "firstName": "John",
    "lastName" : "Doe",
    "_embedded" : {
        "address" : {
            "city" : "Paris",
            "country" : "France",
            "street" : "142 Rue de Rivoli",
            "zip" : "75001",
            "_links" : {
                "self" : {
                    "href" : "http://localhost/backbone.hateoas/test/api/addresses/1"
                }
            }
        }
    },
    "_links" : {
        "address" : {
            "href" : "http://localhost/backbone.hateoas/test/api/addresses/1"
        },
        "self" : { 
            "href" : "http://localhost/backbone.hateoas/test/api/users/1"
        }
    }
}

urlRoot

When you work with HAL you often have a root URL which is global to your API, in most cases this URL is the root of a catalog (i.e a special resource describing the documentation of your endpoints and how to access them).

The Hal.urlRoot property allows you to configure this global API root URL (please note that the name urlRoot has been chosen to be the same as the Backbone Model urlRoot parameter).

For exemple if our API is located at https://myserver.com/api then we could declare the following Hal.urlRoot.


Hal.urlRoot = 'https://myserver.com/api';

After that Hal.Collection and Hal.Model can automatically generate absolute API urls for you.


var users = new Hal.Collection();
users.urlMiddle = 'users';

// Fetch 'https://myserver.com/api/users'
users.fetch(); 

// Hal.Model can also be used easily without any associated collection
// This will fetch 'https://myserver.com/api/users/1'
var john = new Hal.Model();
john.urlMiddle = 'users';
john.set('id', 1);
john.fetch();

// If you want you can "force" use of other absolute URLs
// This will fetch 'https://myserver2.com/api/users'
users.url = 'https://myserver2.com/api/users';
users.fetch();

// This will fetch 'https://myserver2.com/api/users/jdoe'
john.urlRoot = 'https://myserver2.com/api/users/jdoe';
john.fetch();

Hal.Model

urlMiddle

The urlMiddle is an additional URL parameter specific to backbone.hateoas and which easier model fetching without being forced to attach your model to a collection.

The urlMiddle is used only when your model is not linked to a collection having a URL and which do not define a specific urlRoot property.

The urlMiddle is used to create an absolute URL (with the url() method) equal to the concatenation of the Hal.urlRoot property plus the urlMiddle.

Here is an exemple :


Hal.urlRoot = 'https://myserver.com/api';

// This will fetch 'https://myserver.com/api/users/1'
var john = new Hal.Model();
john.urlMiddle = 'users';
john.set('id', 1);
john.fetch();

// This will fetch 'https://myserver.com/api/companies/2/users/1'
john = new Hal.Model();
john.urlMiddle = 'companies/2/users';
john.set('id', 1);
john.fetch();

toJSON

By default the Backbone toJSON([options]) method do not accept or use any option, a Hal.Model can use an additional contentType option which can be equal to application/json or application/hal+json.

When the toJSON([options]) method is called with this option it overwrites the Hal.contentType configuration only for this call.

In most cases this is useful when you when to store a serialized version of a resource somewhere (in the local storage for example).

Hal.Collection

The backbone.hateoas Hal.Collection class is an opinionated class to manipulate HAL collection resources, what we call a HAL collection is a resource having the following structure :


{
    "page" : 1, 
    "page_count" : 6,
    "page_size" : 12,
    "total_items" : 65,
    "_links" : {
        "self" : {
            "href" : "http://myserver.com/api/users?page=3"
        },
        "first" : {
            "href" : "http://myserver.com/api/users?page=1"
        },
        "last" : {
            "href" : "http://myserver.com/api/users?page=133"
        },
        "previous" : {
            "href" : "http://myserver.com/api/users?page=2"
        },
        "next" : {
            "href" : "http://myserver.com/api/users?page=4"
        } 
    },
    "_embedded" : {
        "users" : [
            { ... },
            { ... },
            ...
        ]
    }
}

The constraints imposed to have a valid HAL Collection are the following :

  • The HAL resource MUST HAVE a page property (or index)
  • The HAL resource MUST HAVE a number of pages (or page count) property
  • The HAL resource MUST HAVE a page size property
  • The HAL resource MUST HAVE a total number of results property
  • The HAL resource MUST HAVE ONLY ONE embedded array property representing the elements of the current collection's page

So a Hal.Collection object automatically manages paginated collections, the name of the attributes page, page_count, page_size and total_items have been chosen to be compliant with Apigility.


var UserCollection = Hal.Collection.extend({
    model : function(attrs, options) {
        if(attrs.type === 'STUDENT') {

            return new Student(attrs);

        } else if(attrs.type === 'TEACHER') {

            return new Teacher(attrs);

        }
    }, 
    rel : 'users', 
    url : 'http://myserver.com/api/users'
});
var users = new UserCollection();
users.getFirstPage();
users.getLastPage();
users.getPreviousPage();
users.getNextPage();

Embedded

Release history

0.1.0-beta.3

  • First version published on http://npmjs.org, this version do not add any new functionality.

0.1.0-beta.2

  • Fix #5 - toJSON() fails on embedded resources after an unsetEmbedded() call has been done.
  • Fix #8 - Null or undefined links should be authorized.

0.1.0-beta.1

  • Fix #11 - urlMiddle should not be encoded.

0.1.0-alpha9

  • Implement hasEmbedded(rel) and hasLink(rel) methods on the Hal.Model class ;
  • Implement the clone() method for Hal.Model ;
  • Setup Travis builds.

0.1.0-alpha8

  • Fix a bug in the toJSON() method, the contentType option was not propagated to embedded resources.

0.1.0-alpha7

  • Allow the toJSON() method to take a contentType option to configure how to serialize a resource, this is useful when you when to serialize an HAL resource to persist it in the local storage for example.

0.1.0-alpha6

  • Allow the toJSON() methods to return plain JSON or HAL JSON ;
  • Add a new global Hal.contentType parameter to configure the behavior of the toJSON() methods ;
  • Add documentation to explain how to manipulate embedded resources ;

0.1.0-alpha5

  • Fix documentation, we talked about a middleUrl parameter but it was urlMiddle ;
  • Fix several serialization / deserialization bugs.

0.1.0-alpha4

  • Add a global Hal.urlRoot parameter ;
  • Add a new urlMiddle parameter to be used with Hal.Collection and Hal.Model ;
  • Fix several toJSON() method implementations ;
  • Now the Hal.Model class is initialized in the set(key, val, options) method instead of initialize(options) ;
  • Fix embedded array resource parsing ;
  • Code coverage is better

0.1.0-alpha3

  • Fix AMD dependency problem with Underscore.

0.1.0-alpha2

  • Add _links parsing and management ;
  • Add _embedded parsing and management ;
  • Continue user documentation.

0.1.0-alpha1

  • First alpha release.