Last Updated: September 09, 2019
·
9.924K
· smallhadroncollider

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)

4 Responses
Add your response

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.

over 1 year ago ·

I completely misread the operator precedence table. Good point!

over 1 year ago ·

You don't need an assignment on _.extend and you could just use _.result instead of _.isFunction?

over 1 year ago ·

that helped a lot, thanks! id like to add to make a return of backboneSync, so .then works.

over 1 year ago ·