Creating a dynamic modular multi-page app with Backbone.js and RequireJs

So, as part of my To-Learn list for 2013 I’m currently messing around with Javascript tools and libraries.

It has been a long way since we used Javascript mostly to validate forms and show alert boxes.

Today we have so many good tools that allows us to write better front-end applications, that’s hard to keep up.

Recently I’ve been playing with Backbone.js, an awesome library that helps you separate content/logic in your front-end application. And while I’m no expert on it, one of the first things I noticed is that Backbone doesn’t explain how you should lay out your code.


This can be good, but it can also lead to architecture design disasters if not used correctly. Specially if you are developing multi-pages website.

So, in this tutorial I’m going to show you how to create a good foundation for your application using AMD design pattern with Backbone.js and RequireJs.

Notice: This is not a tutorial for beginners on BackboneJs or RequireJs. It's expected that you have some experiencie with these tools.

TL;DR

I show you how to create a basic boilerplate multi-page using Backbone.js and RequireJs.
I’ve placed a improved version of this tutorial on github.

Intro

I’m not going to explain in full detail what each of these libraries do, I assume most of you already know it, if not, just click on the likes above.

We’re going to create a sort of boilerplate, that can be reused in future projects, so in order to do this we will need a few extra things to keep everything tidy.

Bower Setup

The first thing you want to do, is to install Bower. This is a very light and solid package manager for the web. You can install/update/remove external vendor packages from your command line.

From your command line, create a folder somewhere in your web root folder.

$ mkdir myapp && cd myapp/

At the root of myapp/ create a new file called “.bowerrc” and add this

{
  "directory": "src/scripts/vendors"
}

This will make bower install it’s dependencies under the “directory” param.
Save the file and go back to the command line.

Create another file called “component.json” and past this.

{
	"name": "MyApp",
	"version" : "1.0.0",
	"dependencies": {
		"jquery": null,
		"backbone-amd": null,
		"underscore-amd": null,
		"requirejs": null,
	}
}

This file will allow Bower to install all of this dependencies for us.

Let the magic begin. From the command line type:

$ bower install

You should start see Bower fetching all those libraries from git and copying them into “src/scripts/vendors”.
By default, Bower copies the entire repository of each library. So it’s up to you if you want to delete some extra files you don’t want. You can also just specify a single file, but read the Bower documentation for more info about this.

Defining a directory structure

Now, let’s create our bundle root folder.

$ mkdir src/scripts/bundles

This folder will be the home of our bundles.

$ mkdir build/

This folder will contain our building scripts, that will merge and minify our dirs into an output dir.

Loading…

Now, let’s get our hands dirty.
Open up your favourite code editor, and create a file “src/index.html”

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name='viewport' content='width=device-width'/>
    <title>Backbone Boilerplate</title>
    <script data-main="scripts/main" 
        data-root="/myapp/src/"
        src="scripts/vendors/requirejs/require.js"></script>
</head>
<body>

    <h1>My title</title>

</body>
</html>

Notice that data-root is a custom field we add. This field defines the default web url root path. If your root path is “/” you can remove this.

data-main is the main entry point for RequireJs, this will load the file “src/scripts/main.js”, notice that we didn’t need to add “.js” extension. RequireJs does this for us.

So let’s create this file.

(function () {

    'use strict';

    requirejs.config({
        baseUrl: "scripts/",
        paths: {
            "jquery": "vendors/jquery/jquery",
            "underscore": "vendors/underscore-amd/underscore",
            "backbone": "vendors/backbone-amd/backbone",
        }
    });

    require(['app'], function (app) {
        app.initialize();
    });

}());

This file will be responsible for creating a config structure that will be used by RequireJs.

On line 17, RequireJs will need to include a “app.js” file, so let’s create it.

define([
    'jquery',
    'underscore',
    'backbone',
    'router'
], function ($, _, Backbone, router) {

    'use strict';

    // Add your modules routing here
    router.route("/*", "home", function () {
        this.loadModule("bundles/todos/main");
    });

    var root = $("[data-main][data-root]").data("root");
    root = root ? root : '/';

    return {
        initialize: function () {
            Backbone.history.start({
                pushState: true,
                root: root
            });
        }
    };
});

This file will allows to create our routing patterns for our bundles.
Inside the define() function, you’ll notice that it will include our dependencies and a new file, called “router”, you haven’t created this yet.
It will also start the Backbone History. Also, notice that it’s using our custom data-field “data-root” around line 15.

To complete our basic dynamic loading, we just need to create one last file. The router.js.

define(["underscore", "backbone"], function (_, Backbone) {

    'use strict';

     // Router
    var Router = Backbone.Router.extend({
        loadModule: function (module) {
            require([module], function (module) {
                module();
            });
        }
    });

    return new Router();
});

This file will create a new Object from Backbone.Router and add a new function called loadModule(). This function will load our Bundles dynamically. If you look at the file “app.js” you will see the following code.

router.route("/*", "home", function () {
    this.loadModule("bundles/demo/main");
});

This must be added by you. What this means is that everytime a url matches “/*” this will trigger an event that will load the “bundles/todos/main.js” file. We haven’t created this yet.

And we are done with the basic foundation for our multi-page app.
To wrap it up, here’s the file tree.

.
├── .bowerrc
├── component.json
└── src
    ├── index.html
    └── scripts
        ├── app.js
        ├── bundles
        ├── main.js
        ├── router.js
        └── vendors

Creating our bundle

The idea behind a bundle is to isolate specific sections of our websites in it.
For example.

http://www.myhost.com/ —> /src/scripts/bundles/home
http://www.myhost.com/users —> /src/scripts/bundles/users
http://www.myhost.com/invoices —> /src/scripts/bundles/invoices

And since we are using requireJs, we can easily create dependencies and share resources between bundles.

So let’s create a basic bundle.
Inside “myapp/src/scripts/bundles” create a directory named “demo” with the following structure

.
├── collections     --> Backbone Collections
├── models          --> Backbone Models
├── templates       --> Mustache Templates
├── tests           --> To store unit tests
├── views           --> Backbone Views
└── main.js         --> Bundle entry point

The main.js required by all bundlers. It will be used as the entry point when a bundle is loaded.

Here’s an example

define([ "./views/main"], function (MainView) {

    'use strict';

    return function () {

        var mainView = new MainView();
    };

});

This file will load a Backbone view object from “views/main.js” file.

Building

“Building” means that we will merge and minify/uglify our source code inside “myapp/src/” and output into a new folder “myapp/dist”.

We will endup with a single merged file for each bundle, and a single common file for our global dependencies like Backbone.js, RequireJs, jQuery and so on.

For eg. All of our files inside “myapp/src/scripts/bundles/demo” will be merged into one “main.js” file in the output directory.

As you know, this should be used on production only.

The major difference here, is that since we are using this for multi-page websites, we really don’t want to merge every thing into one big file, or having all bundles include the common libraries.

So by using the scripts below, we will organize our output code into single files that are loaded on demand.

When we created our directory structure, we created a dir at “myapp/build”.
Inside that directory create two new files

({
	appDir: '../src/',
	baseUrl: './scripts/',
	dir: '../dist',
	mainConfigFile: '../src/scripts/main.js',
	optimizeCss: 'none',
	paths: {
		requireLib: '../scripts/vendors/requirejs/require'
	},
	modules: [
		// WARNING: Do not remove this entry, it will be required
		// by all your bundles.
        {
            name: 'main',
            include: ['requireLib', 'main', 'app'],
        },


        //
        // Add your bundles here.
        // Make sure you always exclude 'main', 
        // unless you want to have
        // one single big file with every bundle 
        // and dependency. This is
        // not recommended.
        //
        {
            name: 'bundles/demo/main',
            exclude: ['main']
        }
    ]
})

In a nutshell, what we are doing here is specifying which bundles should be included, and make sure none of the bundles are merged with the “main” common scripts. Resulting in lighter files for the bundles.

r.js -o app.build.js skipDirOptimize=true
rm -rf ../dist/scripts/vendors
rm ../dist/scripts/router.js
rm ../dist/scripts/app.js
rm ../dist/build.txt

This is the build bash script. It will run r.js and output the result into “myapp/dist” and remove some unused folders and files.

You need to install r.js first.

$ cd build/ 
$ chmod +x build.sh
$ ./build.sh

You should start seeing a bunch of files being merged, and minified.
After it’s completed, you should see a new folder “myapp/dist”. It should contain all of your code, but without the vendors directory and other files like “router.js”, “app.js”.

Instead you should see only a “main.js” file. If you open this file it should include all the common libraries, including require.js

So in your new “myapp/dist/index.html” file you can change the script to

    <script data-main="scripts/main" 
        data-root="/backbone-boilerplate/src/"
        src="scripts/main.js"></script>

because “scripts/vendors/require/require.js” no longer exists.

Conclusion

And there you have it.
A basic boilerplate form multi-page websites using Backbone and Requirejs.

I’ve created a more completed boilerpate called Backbone-Boilerplate (creative name right?) that includes the concepts we are described here and goes a little further by using Unit testing, Template engine and including a Todo demo bundle for your to check it out.

I highly recommend your check this repository out, as it includes a more detail information on how to create bundles.

I also hope you’ve learned something, and if I missed or you have other suggestions please let me know in the comments below.

  24 comments for “Creating a dynamic modular multi-page app with Backbone.js and RequireJs

  1. Ron Reiter
    January 28, 2013 at 1:47 pm

    Not a good idea to link module loading and routing. The whole app should be loaded, and the routing should only switch between states of the application. The router should depend on the application view, and should not initialize before the application is loaded.

    • Henrique B.
      January 28, 2013 at 2:08 pm

      You are right if you were to use it in a single-page app. This however is to make it work for multi-pages apps. I have done this way in order to avoid having this big compiled JS file with all the merged modules in it.

      By using this, you only load the code needed for that particular page.

      • March 29, 2013 at 9:45 am

        Agree! I was looking for this “on demand loading” solution for days..
        I think this is the definite way to load subpages in your Backbone application without polluting the memory with unnecessary assets.

  2. Philip Thrasher
    February 22, 2013 at 1:19 pm

    Great article. Ron, you’re mostly correct. However, what if I’m building a larger web application wherein I have different “areas” that are completely different functionality. For instance, what if I’m developing google docs. I’d need code minified for spreadsheet, but separate code minified for the presentation designer app. They may have some shared code, but there would also be a ton of code you wouldn’t want to load in both instances.

    • June 3, 2014 at 8:00 am

      You made my day! Great article, exactly what I was looking for. I made our backbone app working in the same way!

  3. Phil
    March 14, 2013 at 1:45 am

    So the whole idea here is to use data-root to do 2 things: (1) load the module in that path, (2) change pushState to that url.

    So what prevent me to use tbranyen’s version of Backbone Boilerplate and modify this file to load module based on the route then? https://github.com/tbranyen/backbone-boilerplate/blob/master/app/router.js

    It’s basically the same thing, isn’t it?

  4. Charlie
    April 15, 2013 at 9:19 pm

    Great article and very enlightening, Henrique.

    I’m trying to understand what you meant to say with “module()” in the “loadModule” function of “router” module. did you mean: return module. I recently started to work with AMD so I apologize if this seems too obvious but I just can’t see it. thanks.

    • April 17, 2013 at 11:51 pm

      Look this: require([module], function (module) { module(); });

      The first module is the string “bundles/home/main”, the second is a parameter of the callback function, it will be what is returned by “bundles/home/main” (because RequireJS is designed to behave this way). In this case, it is a function(), because the main.js file returns a function (look at it). So, the third module, is not the string “bundles/home/main”, it is actually the function() returned. As it is “module()”, it is actually just executing the function returned.

      Hope I helped.

  5. Walter Macambira
    April 17, 2013 at 9:20 pm

    I am not an expert on this subject, but I will suggest a way to avoid memory leaks due to views event bindings and other bindings to DOM elements. To achieve this, you need to add in your loadModule function a view transition mechanism that would be responsible for calling a “clear” method from your current view. For this, your main.js (for each bundle), instead of returning a function with the MainView instantiation, would return the view itself. So your router would be aware of which MainView-bundle is loaded and be responsible for calling its “clearing” method to avoid memory leaks. This way, loadModule would also be responsible for adding the view’s el to your app container element. Hope I helped!

    By the way, greate article, helped me a lot understanding a better approach for a multi-page app with Backbone.

    • Charlie
      April 21, 2013 at 8:04 pm

      Definitely helpful, Walter!

      I took another look at RequireJS docs, too. I’m used to returning object from modules.

  6. April 25, 2013 at 5:06 pm

    @ Henrique Great article! I’m currently struggling with Backbone’s lack of organization in a large single page app which really has multiple large views/pages/sections. What you’ve written makes me feel like I’m not crazy for feeling like the app isn’t quite the same as the single view todo apps I’ve seen every where. Do you have any examples where you switch between pages? I noticed that you only have 1 bundle defined so the app really only has 1 page. Managing the transitions between various pages is tricky at best and messy and awkward at worst.

    • Henrique B.
      April 26, 2013 at 8:44 am

      Hi Jon, what I meant was that each page would be a bundle. So say /(index) would be a “home” bundle, /users would be a “users” bundle and so on.

      Eg:

      router.route(“/users”, “users”, function () {
      this.loadModule(“bundles/users/main”);
      });

      Hope this helps

  7. May 2, 2013 at 4:02 am

    Thanks for the boilerplate, Henri. Using Brunch you can get it all for free, as shown in my article Developing modern web applications on Windows with Vagrant. Hopefully some of your readers may find it helpful.

  8. May 16, 2013 at 12:47 am

    Articolo interessante e colgo l’occasione per complimentarmi per questo sito! veramente ben fatto e con tanti articoli utili!

  9. June 9, 2013 at 2:19 pm

    By using “removeCombined: true” in your “app.build.js” the optimizer will remove concatenated files from the output directory and there would be no need for those rm commands in your build.sh

    I think this is necessary when the project scales.
    Thanks for your great article.

  10. June 16, 2013 at 11:34 am

    Amazing whip! I need to trainee when you modify your website, exactly how might i join for your blog page? Your profile assisted us a pertinent cope. We were slightly familiar in this a person’s transmitted offered glistening distinct strategy

  11. Marcus
    July 8, 2013 at 1:07 pm

    Hi Henrique, great article, but I have some questions and problems. I have load the example code, but it doesn’t work on my local mashine. When I type in a new ToDo, nothing happens. Whats wrong. Is there a new version of backbone?

    Another thing is, when i type http://www.url.de/ the root variable is /. That’s ok, but when I type http://www.url.de/user it comes a 404 page. When I use the backbone router standard (www.url.de/#/user) the root variable is /.

    For each page I need a router part:
    Router.route(“/*”, “home”, function () {
    console.log(‘base route’);
    });
    Router.route(“/user”, “user”, function () {
    console.log(‘user route’);
    });
    and so on. Is this right?

    I no route is catching, the default is(?):
    Router.route(“/*”, “home”, function () {
    console.log(‘base route’);
    });

    I hope you can help me.

  12. Marcus
    July 8, 2013 at 1:32 pm

    ahh damn it. I see my mistake. I must create a single html file for each page and set the paths. ok i’ll try the next steps :D

  13. Marcus
    July 8, 2013 at 3:43 pm

    Ok the routing is my problem. I don’t understand the process.

    My URL is: http://myapp.de/projectname/source/
    When I call http://myapp.de/projectname/source/demo it requests automaticly the demo.html. I don’t know why, but the loadModule() function isn’t start. When I use the route with a expression, it works:

    AppRouter.route(/^(.*?)\/demo/, “demo”, function () {
    console.log(‘demo’);
    this.loadModule(“../modules/demo/js/main”);
    });

    Do you see my mistake?

  14. EduardoC
    July 16, 2013 at 2:32 am

    Gracias por su gran artículo, excelente

    Thanks for your great article

  15. October 15, 2013 at 2:12 am

    I unscrewed the finish with the soldering iron and
    screwed in a round flat metal piece for that bowl (regarding the measurements a fifty-cent piece; it has a lip
    ~3mm high that runs across the edge). Besides from that, many of us are one in this increased awareness
    for health. ‘ When this balloon mechanism is filled with vapor, the machine can be turned off.

  16. October 17, 2013 at 6:56 pm

    My friend smiled and told me to look into this blog post and i noticed it relatively impressive. I will bookmark it for a future needed along with i might tell a friends to examine this blog post. Thanks plenty for writing.

  17. Troland
    December 26, 2013 at 2:02 am

    Greate article.while i think the file structure seem not semaic..

  18. April 14, 2014 at 3:31 am

    Thanks for the post. I’m building a multi-page application, wanted to know your ideas around these:
    1. CSS – Do you split the CSS based on the page i.e. instead of loading CSS file for the entire site do you split based on the modules. If yes, how are you loading or what is the strategy around it.
    2. Models – More often than not, the models are shared between the pages (Domain Driven Design), how are you addressing the same. Whether these models are available across the application or are specific to pages.

Leave a Reply

Your email address will not be published. Required fields are marked *