Using HATEOAS and Backbone.js
Asked Answered
M

4

12

I've started experimenting with Backbone.js, and was struck by the documentation for the documentation for the url property on Backbone.Model.

In particular, I'm building out a REST API that uses HATEOAS/hypermedia to drive the client(s).

I can see the usefulness of Backbone's default behaviour of building up URLs itself for items in a collection, but for my case, would prefer to have the model URLs built out of the data that is parsed.

Has anyone extended/built on Backbone to make it do this? Maybe building upon a "standard" like HAL?

EDIT:

For clarification, let's say I have the following:

GET /orders >>

[
  {
     "_links": {
       "self": "/orders/123"
     }
     "name": "Order #123",
     "date": "2012/02/23"
  },
  {
     "_links": {
       "self": "/orders/6666"
     }
     "name": "Order #666",
     "date": "2012/03/01"
  },
]

and I have an Order model like:

var Order = Backbone.Model.extend({
});

I would like the url property to be automatically pulled out of the "self" reference in the HAL. I think creating a new base model something like (not tested):

var HalModel = Backbone.Model.extend({
  url: function() {
    return get("_links").self;
  },
});

Thoughts?

Malaspina answered 1/3, 2012 at 16:41 Comment(2)
Are you saying that if you have an order model that you want the Url to be set dynamically to 'order/{orderid}' and if you have a customer model, the same code would set it to 'customer/{customerid}' ?Sough
@Sough See my edit for more clarification.Malaspina
S
4

Thanks for the clarification @Pete.

I think I see what your proposing and I suppose it could work. However, in your example, you first had to know the /Orders url before you were able to get the orders. And if you reworked your json to have an id property, you'd be pretty close to the default implementation of backbone.

Now if you just want to use a generic model or base model (e.g. HALModel) and just bootstrap it with data, your approach could be useful and definitely could work. However, I would look at overriding parse to pull the url out and set it on the model:

parse: function(response) {
    this.url = response._links.self;
    delete response._links;
    return response;
}
Sough answered 2/3, 2012 at 2:54 Comment(5)
Yes, knowing the /orders URL comes from a GET to the base URL for the service, which using a HATEOAS approach would return the links for all the available resources:GET / >> { "_links": { "urn:mysite.com:orders": "/orders" }Malaspina
I like the use of the parse function. Do I need to chain up to the base parse method with Backbone.Model.prototype.parse.call(this, response); ?Malaspina
It's not necessary to chain up. By default parse return response.Sough
@Malaspina how would this then be called when instantiating a model? Could you provide a full example? ie: the entire model declaration and implementation? ThanksTsarevna
@TomGillard At this point, i would recommend using Mike Kelly's backbone.hal library.Malaspina
S
10

I've extended backbone to do exactly this, the library is available here:

https://github.com/mikekelly/backbone.hal

Shawanda answered 30/5, 2012 at 21:56 Comment(0)
S
4

Thanks for the clarification @Pete.

I think I see what your proposing and I suppose it could work. However, in your example, you first had to know the /Orders url before you were able to get the orders. And if you reworked your json to have an id property, you'd be pretty close to the default implementation of backbone.

Now if you just want to use a generic model or base model (e.g. HALModel) and just bootstrap it with data, your approach could be useful and definitely could work. However, I would look at overriding parse to pull the url out and set it on the model:

parse: function(response) {
    this.url = response._links.self;
    delete response._links;
    return response;
}
Sough answered 2/3, 2012 at 2:54 Comment(5)
Yes, knowing the /orders URL comes from a GET to the base URL for the service, which using a HATEOAS approach would return the links for all the available resources:GET / >> { "_links": { "urn:mysite.com:orders": "/orders" }Malaspina
I like the use of the parse function. Do I need to chain up to the base parse method with Backbone.Model.prototype.parse.call(this, response); ?Malaspina
It's not necessary to chain up. By default parse return response.Sough
@Malaspina how would this then be called when instantiating a model? Could you provide a full example? ie: the entire model declaration and implementation? ThanksTsarevna
@TomGillard At this point, i would recommend using Mike Kelly's backbone.hal library.Malaspina
G
2

I complement here the response of Simon to explain how to easily do it using gomoob/backbone.hateoas.

// 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" 
        },
        self: {
            href: "http://localhost/api/users/1"
        }
    },
    _embedded: {
        address: {
            "city" : "Paris",
            "country" : "France",
            "street" : "142 Rue de Rivoli",
            "zip" : "75001",
            "_links" : {
                "self" : {
                    "href" : "http://localhost/api/addresses/1"
                }
            }
        }
    }
});

// Now we you can easily get links, those lines are equivalent
var link1 = user.getLink('avatar');
var link2 = user.getLinks().get('avatar'); 

// So getting self link is simple too
var self = user.getLink('self');

// All the Hal.Link objects returned by backbone.hateoas are in fact 
// standard Backbone models so its standard Backbone
link1.get('href');
link1.getHref();

// You can do more with shortcut methods if your HAL links 
// have more properties
link1.get('deprecation');
link1.getDeprecation();
link1.get('name');
link1.getName();
link1.get('hreflang');
link1.getHreflang();
link1.get('profile');
link1.getProfile();
link1.get('title');
link1.getTitle();
link1.get('type');
link1.getType();
linke1.get('templated');
link1.isTemplated();

// You can also manipulate embedded resources if you need
user.getEmbedded('address').get('city');
user.getEmbedded('address').getLink('self');
...

Finally we provide an Hal.Model.url() implementation which is more powerful than standard Backbone url() and which is very useful if you use HAL.

// By default url() returns the href of the self link if this self 
// link is present
user.url(); 

// If the self link is not defined then url() has the same behavior 
// as standard Backbone url() method
// My user is link to a user collection having a URL equal to 
// 'http://localhost/user1'
user.url(); // http://localhost/users/1

// My user is not link to a user collection in this case the URL is 
// generate using the model urlRoot property by default
user.urlRoot = 'http://myserver/users';
user.url(); // http://localhost/users/1

// backbone.hateoas also allows you to define an application wide root 
// URL which prevent to use absolute URLs everywhere in your code
Hal.urlRoot = 'http://localhost/api';  // HAL root API URL

var user = new Hal.Model({ id : 1});
user.urlMiddle = 'users'; 
user.url(); // http://localhost/api/users/1

Hope this helps, don't hesitate to post issues on our github if you need help on this.

Guyguyana answered 2/7, 2015 at 5:36 Comment(0)
M
1

You can override the url function on the model to calculate the URL however you want; it's completely extensible.

Mandibular answered 1/3, 2012 at 17:41 Comment(1)
Yeah, I've seen I can override it manually for a particular model. I'm curious if anyone has a more generic/reusable approach they've taken.Malaspina

© 2022 - 2024 — McMap. All rights reserved.