Add a root URL to all Backbone API request
TL;DR: You can over-ride (not over-write) the Backbone.sync
function to insert the root URL for all requests.
During development of a Backbone app it's not uncommon for the API to be hosted on a different server from the code you're writing (your Backbone code is most likely on localhost
, whereas the API could be anywhere else on the net). This means you'll want to add the root URL to all your requests.
A quick look on Stack Overflow suggests that most people get around this by adding the root URL to the url
/urlRoot
property of every single model/collection. This works, but it's very inefficient: even if you're storing the root URL in a variable somewhere (or by using a configuration file) you're still repeating the code to add it to every single model/collection that you write.
A Mixin
One option would be to setup an abstract RootURL
mixin object that has a url
function which all models and collections use:
rootURL.js
// Using AMD/RequireJS
define(function () {
/*
* Create a `url` method that returns the root url
* plus a `stubURL` property
*/
url: function () {
return 'http://root.url/' + this.stubURL;
}
});
EventModel.js
// Using AMD/RequireJS
define(['rootURL'], function (rootURL) {
/*
* Mix the rootURL object into the new Model
* and store the `url` in the `stubURL` property
*/
return Backbone.Model.extend({
stubURL: 'events'
}, rootURL);
});
This will work, but you'll have to remember to use the mixin every time you add a new model/collection. All in all, it's better than repeating the url
code in every single model/collection, but it's not great.
Over-ride Backbone.sync
There is a much neater way of setting the root URL that takes advantage of the fact that the options
argument that you pass to Backbone.sync
is what is then passed to the jQuery ajax
function. The jQuery ajax
function takes a url
property, and this is set by Backbone.sync
. However, if you set the url
property explicitly it will over-ride what Backbone.sync
sets.
By storing a copy of the original Backbone.sync
function and then over-riding it with our own version (which simply sets the url
property and then calls the original), we can set this on every API call without having to explicitly add it every time:
// Using AMD/RequireJS
define(['config'], function (config) {
'use strict';
// Store the original version of Backbone.sync
var backboneSync = Backbone.sync;
Backbone.sync = function (method, model, options) {
/*
* Change the `url` property of options to begin
* with the URL from settings
* This works because the options object gets sent as
* the jQuery ajax options, which includes the `url` property
*/
options = _.extend(options, {
url: config.api.url + (_.isFunction(model.url) ? model.url() : model.url)
});
/*
* Call the stored original Backbone.sync
* method with the new url property
*/
backboneSync(method, model, options);
};
});
(This assumes you've setup a config file)
Written by Mark Wales
Related protips
4 Responses
Hi Marc,
Could it be that there are some brackets missing? Something like this:
url: config.api.url + (_.isFunction(model.url) ? model.url() : model.url);
Otherwise, the url-option is set to the model.url (output from the function or the property, and nothing is prepended.
I completely misread the operator precedence table. Good point!
You don't need an assignment on _.extend and you could just use _.result instead of _.isFunction?
that helped a lot, thanks! id like to add to make a return of backboneSync, so .then works.