How to structure your Backbone app with require.js

Kane Cohen
4 min readAug 10, 2014

Backbone — one of the most popular javascript MV* frameworks in the market. You load it up and can start writing new code right away anyway you want. Backbone won’t force you into some arbitrary structure and that’s why I personally like it. This is great for small applications — you can keep all of the stuff in one file and it’ll be totally manageable. Great, but what if we decided to use it for something more complex? How can we keep our code organized with freedom-loving Backbone? I’ll describe one of the approaches below.

UPDATE: Since publication of this post I’ve moved to browserify-based development when all of the code is being written with node.js modularity in mind.

If your application grows to the medium size, you might consider splitting javascript files by their purpose: views.js to keep all your views (usually the largest part of the app), models.js to store collections + models and last one — router.js (or controller.js) to store router and controller methods.

But, what are we to do if we chose Backbone for large app? Something with dozens of large and small views, a lot of models and collections with its own helper methods?

We split further. Let’s give every object its own file to reside — just like in any back end framework. At this point loading all the .js files would be a bad design decision— that’s why we are going to use require.js (I assume reader knows it). Directory structure might look something like that:

If you previously worked with require.js then you might ask — how all of these files going to communicate with each other? How can we tell view to create another sub view? Or how to tell collections where its models are?

There’s a couple of ways to achieve global app context and communication without putting anything in actual global scope. Here’s a snippet any node.js dev might be familiar with:

// ./app/views/index.js
define([‘backbone’, ‘./comment’, ‘./post’], function(
Backbone,
comment,
post) {
return function(app) {
app.init.views = {
// Notice how we are sending backbone and app.
// You can send only app and load Backbone
// instance in each view itself.
comment: comment(Backbone, app),
post: post(Backbone, app)
};
};
});
// In node.js it would looks something like that:
/**
module.exports = function(app) {
app.init.views = {
comment: require('./comment')(app),
post: require('./post')(app)
};
};

Let me explain what is going on over here if you do not recognize it. We’ll start from the moment require.js loads main.js file for out app.

Imagine a cascade (based on the image of a structure above): first, require.js loads up main.js file, and it loads up app.js file. That is a standard require.js behavior. App.js in its turn loads via direct paths index.js files in collections, models and views folders. And index.js files load each collection, model and view.

// ./app/views/comment.js
define([‘utility’], function(util) {
return function(Backbone, app) {
return Backbone.View.extend({
// Your View code.
});
};
});

After loading those index files, we execute them (see below) just like we would do with any function — models(app), collections(app), views(app). Why do we do that? If you look at the context of the index.js file you’ll notice that they themselves load appropriate files and when loaded, include them in an anonymous function which is being returned from index.js files. That anonymous function accepts app parameter which is connected to our main app object — our webapp context.

define([
models/index’, // Important  —  load index.js files via paths.
views/index’,
collections/index’,
‘router’
], function(
models,
views,
collections,
router
) {
‘use strict’;
// Our app context.
var app = {
init: {},
views:{}
};
// Initialize app elements  —  those anonymous functions
// in index.js files. Order of execution is important.
models(app);
collections(app);
views(app);
router(app);

// Rest of your app code.
// Shouldn't really take much space since we moved
// everything to inner files.
});

When executed, each anonymous function takes app context and adds new properties to the app.init — that is where we store non-initialized Backbone objects. That allows us to do new app.init.views.comment() or app.models.comment that could be used in Comments collection. Same goes for any other shared element anywhere where app context is available.

What we achieve with this? We take Backbone which has a tendency to grow in all directions and contain each application element in its own file with its own dependencies (if needed). That would greatly help with development especially for multiple developers each working on his own corner of the app. And don’t forget testing — way easier to unit test each element in its own contained scope.

When you’re ready for production all you need to do is just run require.js optimizer which will compile and minify all of the files in one.

--

--