Categories
Frameworks JavaScript MVC

Introduction to Backbone.js: Structure for JavaScript Apps

Introduction

jQuery made manipulating the DOM easy. But as JavaScript applications grow, jQuery alone isn't enough. You end up with spaghetti code – event handlers everywhere, data scattered across the page, no structure.

Backbone.js solves this. Created by Jeremy Ashkenas (also behind CoffeeScript and Underscore.js), Backbone provides structure for JavaScript applications. It's not a full framework like Rails – it's a library that enforces architectural patterns.

Backbone gives you models, views, collections, and routers. It's the minimal structure you need to build maintainable single-page applications.

What is Backbone.js?

Backbone is a JavaScript library that provides MVC-like structure:

  • Models – Data and business logic
  • Collections – Ordered sets of models
  • Views – UI logic and rendering
  • Routers – URL routing for single-page apps
  • Events – Pub/sub for decoupling

Backbone is small (~6KB), depends only on Underscore.js, and works with any existing JavaScript application.

Why Backbone?

Without structure, JavaScript apps become messy:

// Without Backbone - spaghetti code
$('#save-button').click(function() {
    var name = $('#name').val();
    var email = $('#email').val();

    $.post('/users', {name: name, email: email}, function(response) {
        $('#user-list').append('<li>' + response.name + '</li>');
        $('#name').val('');
        $('#email').val('');
    });
});

Data, UI, and server communication all mixed together.

With Backbone:

var User = Backbone.Model.extend({
    urlRoot: '/users'
});

var UserView = Backbone.View.extend({
    events: {
        'click #save-button': 'save'
    },

    save: function() {
        var user = new User({
            name: $('#name').val(),
            email: $('#email').val()
        });

        user.save();
        this.collection.add(user);
    }
});

Separated concerns, testable code, clear structure.

Installing Backbone

Include dependencies:

<script src="jquery.js"></script>
<script src="underscore.js"></script>
<script src="backbone.js"></script>

Or use npm:

npm install backbone

That's it. No build process, no configuration.

Models

Models contain data and logic:

var User = Backbone.Model.extend({
    defaults: {
        name: '',
        email: '',
        age: 0
    },

    validate: function(attrs) {
        if (!attrs.email) {
            return 'Email is required';
        }
    }
});

// Create instance
var user = new User({
    name: 'John',
    email: '[email protected]'
});

// Get attributes
console.log(user.get('name'));  // John

// Set attributes
user.set('age', 30);

// Listen to changes
user.on('change', function() {
    console.log('User changed!');
});

user.set('name', 'Jane');  // Triggers 'change' event

Models are just JavaScript objects with special powers.

Collections

Collections are ordered sets of models:

var Users = Backbone.Collection.extend({
    model: User,
    url: '/users'
});

var users = new Users();

// Add models
users.add(new User({name: 'John'}));
users.add(new User({name: 'Jane'}));

// Get models
var first = users.at(0);
var john = users.findWhere({name: 'John'});

// Iterate
users.each(function(user) {
    console.log(user.get('name'));
});

// Listen to changes
users.on('add', function(user) {
    console.log('Added:', user.get('name'));
});

Collections handle groups of related data.

Views

Views handle UI:

var UserView = Backbone.View.extend({
    tagName: 'li',
    className: 'user',

    template: _.template('<%= name %> - <%= email %>'),

    initialize: function() {
        this.listenTo(this.model, 'change', this.render);
    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },

    events: {
        'click .delete': 'deleteUser'
    },

    deleteUser: function() {
        this.model.destroy();
        this.remove();
    }
});

// Create view
var user = new User({name: 'John', email: '[email protected]'});
var view = new UserView({model: user});

// Render
$('#user-list').append(view.render().el);

Views separate DOM manipulation from data logic.

Events

Backbone uses pub/sub extensively:

// Model events
model.on('change', callback);
model.on('change:name', callback);
model.on('destroy', callback);

// Collection events
collection.on('add', callback);
collection.on('remove', callback);
collection.on('reset', callback);

// Custom events
var object = _.extend({}, Backbone.Events);
object.on('custom', callback);
object.trigger('custom', arg1, arg2);

Events decouple components.

Syncing with Server

Backbone talks to RESTful APIs:

var User = Backbone.Model.extend({
    urlRoot: '/users'
});

var user = new User({id: 1});

// GET /users/1
user.fetch();

// POST /users
user.save();

// PUT /users/1
user.set('name', 'Jane');
user.save();

// DELETE /users/1
user.destroy();

Backbone handles AJAX automatically.

Collections too:

var Users = Backbone.Collection.extend({
    url: '/users'
});

var users = new Users();

// GET /users
users.fetch();

Server should return JSON.

Routers

Handle URLs in single-page apps:

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'index',
        'users': 'listUsers',
        'users/:id': 'showUser',
        'users/:id/edit': 'editUser'
    },

    index: function() {
        // Show home page
    },

    listUsers: function() {
        // Show user list
    },

    showUser: function(id) {
        // Show user details
    },

    editUser: function(id) {
        // Show edit form
    }
});

var router = new AppRouter();
Backbone.history.start();

Now URLs like #users/123 trigger the appropriate function.

Complete Example: Todo App

Let's build a simple todo application:

Model:

var Todo = Backbone.Model.extend({
    defaults: {
        title: '',
        completed: false
    },

    toggle: function() {
        this.set('completed', !this.get('completed'));
    }
});

Collection:

var TodoList = Backbone.Collection.extend({
    model: Todo,
    localStorage: new Store('todos'),

    completed: function() {
        return this.filter(function(todo) {
            return todo.get('completed');
        });
    },

    remaining: function() {
        return this.without.apply(this, this.completed());
    }
});

var todos = new TodoList();

Item View:

var TodoView = Backbone.View.extend({
    tagName: 'li',

    template: _.template($('#todo-template').html()),

    events: {
        'click .toggle': 'toggleCompleted',
        'click .destroy': 'destroy'
    },

    initialize: function() {
        this.listenTo(this.model, 'change', this.render);
        this.listenTo(this.model, 'destroy', this.remove);
    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        this.$el.toggleClass('completed', this.model.get('completed'));
        return this;
    },

    toggleCompleted: function() {
        this.model.toggle();
        this.model.save();
    },

    destroy: function() {
        this.model.destroy();
    }
});

App View:

var AppView = Backbone.View.extend({
    el: '#todoapp',

    events: {
        'keypress #new-todo': 'createOnEnter'
    },

    initialize: function() {
        this.$input = this.$('#new-todo');
        this.$list = this.$('#todo-list');

        this.listenTo(todos, 'add', this.addOne);
        this.listenTo(todos, 'reset', this.addAll);

        todos.fetch();
    },

    addOne: function(todo) {
        var view = new TodoView({model: todo});
        this.$list.append(view.render().el);
    },

    addAll: function() {
        this.$list.empty();
        todos.each(this.addOne, this);
    },

    createOnEnter: function(e) {
        if (e.which !== 13 || !this.$input.val().trim()) return;

        todos.create({
            title: this.$input.val().trim()
        });

        this.$input.val('');
    }
});

var app = new AppView();

A functional todo app with clean separation of concerns.

Templates

Backbone doesn't force a templating system, but Underscore templates are common:

Template:

<script type="text/template" id="user-template">
    <div class="user">
        <h3><%= name %></h3>
        <p><%= email %></p>
        <button class="delete">Delete</button>
    </div>
</script>

View:

var UserView = Backbone.View.extend({
    template: _.template($('#user-template').html()),

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

Use Mustache, Handlebars, or any other templating engine.

Best Practices

Keep views small: One view per component. Don't create massive views.

Use listenTo instead of on:

// Bad - memory leak
model.on('change', this.render);

// Good - automatically cleaned up
this.listenTo(model, 'change', this.render);

Return this from render: Enables chaining: view.render().$el

Don't reference $el in initialize: Element might not exist yet. Reference in render.

Use model attributes, not DOM: Don't read values from DOM. Store in model.

Common Patterns

Composite Views:

Views that manage child views:

var ListView = Backbone.View.extend({
    initialize: function() {
        this.listenTo(this.collection, 'add', this.addOne);
        this.listenTo(this.collection, 'reset', this.render);
    },

    render: function() {
        this.$el.empty();
        this.collection.each(this.addOne, this);
        return this;
    },

    addOne: function(model) {
        var view = new ItemView({model: model});
        this.$el.append(view.render().el);
    }
});

Subviews:

Store child views:

var ParentView = Backbone.View.extend({
    initialize: function() {
        this.childView = new ChildView();
    },

    render: function() {
        this.$el.html(this.template());
        this.$('.child-container').html(this.childView.render().el);
        return this;
    },

    remove: function() {
        this.childView.remove();
        Backbone.View.prototype.remove.call(this);
    }
});

Testing Backbone Apps

Backbone code is testable:

// Jasmine example
describe('User Model', function() {
    beforeEach(function() {
        this.user = new User({
            name: 'John',
            email: '[email protected]'
        });
    });

    it('has name and email', function() {
        expect(this.user.get('name')).toBe('John');
        expect(this.user.get('email')).toBe('[email protected]');
    });

    it('validates email', function() {
        this.user.set('email', '');
        expect(this.user.isValid()).toBe(false);
    });
});

Models and collections are easy to test.

Backbone vs Other Frameworks

Backbone vs Ember:

  • Backbone is minimalist, Ember is comprehensive
  • Backbone lets you choose, Ember has opinions

Backbone vs Angular:

  • Backbone is a library, Angular is a framework
  • Angular has two-way binding, Backbone doesn't

Backbone vs Knockout:

  • Similar approaches
  • Knockout focuses on declarative bindings

Backbone is lightweight and flexible. Great for applications where you want control.

When to Use Backbone

Good fit:

  • Single-page applications
  • Apps with complex client-side state
  • RESTful backends
  • When jQuery isn't enough

Poor fit:

  • Simple websites (use jQuery)
  • Apps needing two-way binding (consider Ember/Angular)
  • Teams wanting comprehensive framework

Learning Resources

Backbone documentation: backbonejs.org – Excellent annotated source

Developing Backbone Applications: Free online book by Addy Osmani

Backbone Fundamentals: Comprehensive guide

Backbone source code: Read it – it's only 1,600 lines

TodoMVC: See Backbone compared to other frameworks

In Summary

Backbone provides structure without being heavyweight. Models, views, collections, and routers give you architectural patterns without forcing decisions.

It's not a complete framework. You choose templating, build process, and additional libraries. This flexibility is Backbone's strength and sometimes its weakness.

For applications grown beyond jQuery, Backbone offers a clear migration path. Start small – add models for data, views for UI. Refactor incrementally.

Backbone won't solve all problems, but it prevents the spaghetti code that plagues large JavaScript applications. It's structure when you need it, without the weight of a full framework.

Give Backbone a try. Build a small app. Experience organized JavaScript. You might find it's exactly what your project needs.

By Shishir Sharma

Shishir Sharma is a Software Engineering Leader, husband, and father based in Ottawa, Canada. A hacker and biker at heart, and has built a career as a visionary mentor and relentless problem solver.

With a leadership pedigree that includes LinkedIn, Shopify, and Zoom, Shishir excels at scaling high-impact teams and systems. He possesses a native-level mastery of JavaScript, Ruby, Python, PHP, and C/C++, moving seamlessly between modern web stacks and low-level architecture.

A dedicated member of the tech community, he serves as a moderator at LUG-Jaipur. When he’s not leading engineering teams or exploring new technologies, you’ll find him on the open road on his bike, catching an action movie, or immersed in high-stakes FPS games.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.