TL;DR: BackboneJS is a JavaScript library that provides models with key-value bindings and custom events, views with declarative event handling, and collections with an abundant API of enumerable functions. Currently, BackboneJS has over 25,000 stars on GitHub. BackboneJS gives your web applications structure. In this tutorial, I'll show you how to build a web application with BackboneJS and add authentication to it. Check out the repo to get the code.


BackboneJS is a JavaScript library, built by Jeremy Ashkenas and maintained by a team of contributors. It is an open source component of DocumentCloud. It was initially released in 2010. Since then, it has undergone tremendous growth and adoption in the developer community. Over the past 2 years, the use of Backbone have declined due to new libraries and framework like ReactJS, Vue.js and Angular. The latest stable release is 1.3.3 and it was tagged on April 5, 2016. Actively used open source projects and libraries tag releases very often, which signifies growth. However, BackboneJS has been stagnant for over a year now!

Furthermore, there are many web platforms that use BackboneJS to build their frontends. Such platforms include Trello, Bitbucket, Foursquare, SeatGeek and more. The documentation is very comrehensive.

Understanding Key Concepts in BackboneJS

BackboneJS has a philosophy: Keep Business logic separate from User Interface. When the two are entangled, change is hard. The key concepts needed to understand how BackboneJS works are:

  • Models
  • Views
  • Collections
  • Events

I'll give a basic overview of these concepts to nourish your understanding of BackboneJS.

Models

A Model is a layer that transforms and syncs data with a persistence layer (mostly RESTful API interacting with a database). They contain all the helpful functions needed to manipulate some piece of data from the persistence layer. When any of its data is modified, Models trigger change events.

Consider a Model as a sort of manipulator. Instead of directly interacting with the data from your API, it serves as a layer that allows you easily manipulate every tiny piece of data in a very friendly and fantastic way.

Here's a simple case study: Imagine we are in charge of a gift shop and we need to build a database of the gifts that we sell. We need to keep track of the stock of those gifts. Details like the price, brand, stock and type.


var Shop = Backbone.Model.extend({

});

Now, we can create an instance of the Shop model and populate it like so:

var Shop = Backbone.Model.extend({
  brand: 'Rolex',
  type: 'Wrist Watch',
  price: 900
  stock: 8
});

We can also add default attributes to the Model like below:

var Shop = Backbone.Model.extend({
  defaults: {
    brand: '',
    type: '',
    price: 900
    stock: 0
  }
});

Now, we can call get and set methods on an instance of the Shop model. If we want to fetch the brand of the gift, we will retrieve it with this:


var firstShop = new Shop({
  brand: 'Rolex',
  type: 'Wrist Watch',
  price: 900
  stock: 8
});

var secondShop = new Shop({
  brand: 'Michael Korrs',
  type: 'Wrist Watch',
  price: 400
  stock: 16
});


firstShop.get('brand');
// Rolex
secondShop.get('brand');
// Michael Korrs

Let's look at another practical case. If we need to create a new user on the backend, we can instantiate a new User Model and call the save method.

var UserModel = Backbone.Model.extend({
    urlRoot: '/user',
    defaults: {
      name: '',
      email: ''
    }
});

var user = new UserModel();
// Notice that we haven't set an `id`
var userDetails = {
    name: 'Prosper',
    email: 'unicodeveloper@gmail.com'
};

// Because we have not set a `id` the server will call
// POST /user with a payload of { name:'Prosper', email: 'unicodeveloper@gmail.com'}
// The server should save the data and return a response containing the new `id`
user.save(userDetails, {
    success: function (user) {
        alert(JSON.stringify(user));
    }
});

We can fetch the details of a user that has been created with an id with the code below:

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

// The fetch below will perform GET /user/1
// The server should return the id, name and email from the database
user.fetch({
    success: function (user) {
        alert(JSON.stringify(user));
    }
});

Updating the User Model will look like this:

...
...
user.save({name: 'Goodness'}, {
  success: function (model) {
    alert(JSON.stringify(user));
  }
});

Collections

Collections are ordered sets of models. You can bind some events to be notified when any model in the collection has been modified. Events such as change, add, remove.

Let's create a Backbone collection and add models to it.

var ShopCollection = Backbone.Collection.extend({

});

Now, we can set the Shop model to our collection like this:

var ShopCollection = Backbone.Collection.extend({
  model: Shop
});

The next thing will be to instantiate the Collection and the instances of our models like this:

var ShopCollection = new ShopCollection;
ShopCollection.add(firstShop);
ShopCollection.add(secondShop);

To access the models in the collection, you can use each, one of the Underscore methods available to loop over the collection and present the data to the screen. During the iteration, you can perform model methods like get/set on each model.

ShopCollection.each((shop) => {
   ...
   shop.get('brand');
   shop.get('stock');
   ...
});

Other popular methods you can use on a collection are toJSON, and sync.

Views

Backbone views can be used with any JavaScript templating library. It is unopinionated about the process used to render View objects and their subviews into UI. The views handle user input and interactivity, renders data from the model and also sends captureed input back to the model.

The views listen to the model "change" events, and react or re-render themselves appropriately.

Note: Templates allow us to render the User Interface as an alternative to direct DOM manipulation.

var PersonView = Backbone.View.extend({

   tagName: 'li',

   initialize: function(){
     this.render();
   },

   render: function(){
     this.$el.html( this.model.get('name') + ' (' + this.model.get('age') + ') - ' + this.model.get('occupation') );
  }
});

Now, let's use a template

var Person = Backbone.Model.extend({
    defaults: {
        name: 'Prosper Otemuyiwa',
        age: 23,
        occupation: 'Evangelist'
    }
});

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

    my_template: _.template("<strong><%= name %></strong> (<%= age %>) - <%= occupation %>"),

    initialize: function(){
        this.render();
    },

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

Events

Events is a module that can be mixed in to any object, giving the object the ability to bind and trigger custom named events.

var object = {};
_.extend(object, Backbone.Events);
object.on("alert", function(msg) { alert("Triggered " + msg); });
object.trigger("alert", "an event");

In the code above, we copied all of Backbone's events to a JavaScript object using the extend functionality of underscore. So we listen on alert Backbone event and then trigger the event.

Let's try something more practical. Say we have a UserModel,

var UserModel = Backbone.Model.extend({

    initialize: function(){
        this.on('change',this.someChange,this);
    },

    defaults : {
      name : '',
      email : ''
    },

    someChange: function(model,options) {
      alert(‘something has changed’);
    }

});
var newUser = new UserModel({ name:’Raymond Igoladebayi’, email: 'raymond@waya.com'});
newUser.set(‘email’, 'ppp@ppp.com’); // this will trigger the `change` event

In the code above, the user model will now listen to change events on itself. Once you set the name to another value, it triggers the change event that calls the someChange function.

In Backbone, you can also listen to just a specific attribute change. For example, we can listen to only title attribute change. How? We simply chain it with a colon like so: change:title.

...
this.on('change:title', this.someChange, this);
...

The change event is only triggered whenever the title changes. And then calls the someChange function.

We also have built-in events that occur on collections. Check out the catalog of built-in Backbone events.

Our app: Address Book

The app we will build today is an Address Book. This app simply manages the address of employees in an organization. We won't make use of any back-end, but will take advantage of Backbone Collections. I'll leave you to persist it to a backend or use local Storage.

Build The App With BackBoneJS

We'll need three main dependencies, backbone.js, underscore and jquery. Go ahead and create your app directory.

In the root directory, create an index.html file. Also create css, and js folders within the directory.

Inside the js directory, go ahead and create the collections, models and views directories.

Populate the index.html file with the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Address Book</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <link href="app/css/main.css" rel="stylesheet">
  </head>
  <body>
    <header class="bs-header">
      <div class="container">
        <h1>Address Book</h1>
      </div>
    </header>
    <div class="container">
      <div class="row">
        <div class="col-xs-12 main-container">
        </div>
      </div>
    </div>

    <script type="text/template" id="tpl-contacts">
      <h2 class="page-header text-center">List of Employees</h2>
      <p class="text-center">
        <a href="#employees/new" class="btn btn-lg btn-outline">Add Employee</a>
      </p>
      <ul class="media-list row contacts-container"></ul>
    </script>

    <script type="text/template" id="tpl-contact">
      <div class="thumbnail">
        <img class="media-object" src="http://svgavatars.com/style/svg/<%- avatar %>">
      </div>
      <div class="media-heading">
        <h3>
          <%- name %>
          <small>
            <a href="#employees/edit/<%- id %>"><span class="glyphicon glyphicon-pencil"></span></a>
            <a href="#employees/delete/<%- id %>" class="delete-employee">
              <span class="glyphicon glyphicon-trash"></span>
            </a>
          </small>
        </h3>
      </div>
      <div class="media-body">
        <dl>
          <dt>Phone Number:</dt>
          <dd><%- tel %></dd>
          <dt>Email:</dt>
          <dd><%- email %></dd>
        </dl>
      </div>
      <hr>
    </script>

    <script type="text/template" id="tpl-new-contact">
      <h2 class="page-header text-center"><%= isNew ? 'Create' : 'Edit' %> Employee </h2>
      <form role="form" class="form-horizontal employee-form">
        <div class="form-group">
          <label class="col-sm-4 control-label">Full name:</label>
          <div class="col-sm-6">
            <input type="text" class="form-control employee-name-input" value="<%- name %>" required>
          </div>
        </div>
        <div class="form-group">
          <label class="col-sm-4 control-label">Email address:</label>
          <div class="col-sm-6">
            <input type="email" class="form-control employee-email-input" value="<%- email %>" required>
          </div>
        </div>
        <div class="form-group">
          <label class="col-sm-4 control-label">Telephone number:</label>
          <div class="col-sm-6">
            <input type="tel" class="form-control employee-tel-input" value="<%- tel %>" required>
          </div>
        </div>
        <div class="form-group">
          <div class="col-sm-offset-4 col-sm-3">
            <button type="submit" class="btn btn-outline btn-lg btn-block">Submit</button>
          </div>
          <div class="col-sm-3">
            <a href="#employees" class="btn btn-outline btn-lg btn-block">Cancel</a>
          </div>
        </div>
      </form>
    </script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
    <script src="https://cdn.auth0.com/js/auth0/8.8/auth0.min.js"></script>

    <script src="app/js/app.js"></script>
    <script src="app/js/models/employee.js"></script>
    <script src="app/js/collections/employees.js"></script>
    <script src="app/js/views/employee.js"></script>
    <script src="app/js/views/employees.js"></script>
    <script src="app/js/views/employeeForm.js"></script>
    <script src="app/js/router.js"></script>

    <script>
      $(function() {
        EmployeeManager.start({
          employees: [
            {
              id: 1,
              name : 'Christian Nwamba',
              avatar: '11.svg',
              tel: '651-603-1723',
              email: 'chris@scotch.io'
            },
            {
              id: 2,
              name : 'Bukola Ayodeji',
              avatar: '20.svg',
              tel: '513-307-5859',
              email: 'bukolayodeji@nairabet.com'
            },
            {
              id: 3,
              name : 'Rick Ross',
              avatar: '17.svg',
              tel: '918-774-0199',
              email: 'ross@auth0.com'
            },
            {
              id: 4,
              name : 'Godson Ukpere',
              avatar: '19.svg',
              tel: '702-989-5145',
              email: 'g.ukpe@gigstar.co'
            },
            {
              id: 5,
              name : 'John I. Wilson',
              avatar: '01.svg',
              tel: '318-292-6700',
              email: 'JohnIWilson@dayrep.com'
            },
            {
              id: 6,
              name : 'Goodnes Tejufona',
              avatar: '05.svg',
              tel: '803-557-9815',
              email: 'goodness.teju@kudiai.com'
            }
          ]
        });
      });
    </script>
  </body>
</html>

Let's analyze the code above. We have three template-type scripts. One for displaying the list of employees, another for editing and creating new employees and one for displaying the link for creating new employees.

Furthermore, we referenced jquery, underscore and backbone libraries. We also linked some views and script files that do not exist yet. We'll create them soon.

Finally, there is a startup function, EmployeeManager.start(), that takes in an array of employees for display when the app is started. You'll understand how this works in a couple of minutes.

Next, open up the models directory and create an employee.js file.

employee.js

EmployeeManager.Models.Employee = Backbone.Model.extend({
  defaults: {
    name: null,
    tel: null,
    email: null,
    avatar: null
  }
});

We just set the defaults of our model attributes to null. In our app, we are not persisting anything so our model will be very simple.

Next, create an employees.js file in the collections directory and add the following code to it:

collections/employees.js

EmployeeManager.Collections.Employees = Backbone.Collection.extend({
  model: EmployeeManager.Models.Employee
});
``

Earlier I mentioned that collections work with models.

## Create The Views

We'll have three views. One view for handling the list of employees, one view for the details of each employee and one view for adding employees.

Let's get started. Go ahead and open up the `js/views` directory. Create a file, `employee.js`.

_js/views/employee.js_

EmployeeManager.Views.Employee = Backbone.View.extend({ tagName: 'li', className: 'media col-md-6 col-lg-4', template: _.template($('#tpl-contact').html()),

events: { 'click .delete-employee': 'onClickDelete' },

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

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

onClickDelete: function(e) { e.preventDefault(); this.model.collection.remove(this.model); } }); ```

This is the view that handles the details of each employee. In the code above, we have the html tag, <li> that is assigned a class. This is the element that houses the employee detail. The render function ensures that the right data is appended to the template and displays on the page. The onClickDelete function is for removing the data from the model and collection.

The events object simply maps the onClickDelete function to the click event of the delete-employee button.

Next, create the add employee form view. Create a file, employeeForm.js in the js/views directory.

js/views/employeeForm.js

EmployeeManager.Views.EmployeeForm = Backbone.View.extend({
  template: _.template($('#tpl-new-contact').html()),

  events: {
    'submit .employee-form': 'onFormSubmit'
  },

  render: function() {
    var html = this.template(_.extend(this.model.toJSON(), {
      isNew: this.model.isNew()
    }));
    this.$el.append(html);
    return this;
  },

  onFormSubmit: function(e) {
    e.preventDefault();

    this.trigger('form:submitted', {
      name: this.$('.employee-name-input').val(),
      tel: this.$('.employee-tel-input').val(),
      email: this.$('.employee-email-input').val(),
      avatar: '13.svg'
    });
  }
});

In the code above, the onFormSubmit function is called when we submit the form. And this function is called whenever the submit event is fired. The render function assigns the model to the template.

Finally, create a file, employees.js in the js/views directory.

js/views/employees.js

EmployeeManager.Views.Employees = Backbone.View.extend({
  template: _.template($('#tpl-contacts').html()),

  renderOne: function(employee) {
    var itemView = new EmployeeManager.Views.Employee({model: employee});
    this.$('.contacts-container').append(itemView.render().$el);
  },

  render: function() {
    var html = this.template();
    this.$el.html(html);

    this.collection.each(this.renderOne, this);

    return this;
  }
});

This view is responsible for the list of employees on the page.

Create The Router

This is a single page application, we need to make use of some routing capabilities. In the js directory, create a router.js file and add the code below:

js/router.js

EmployeeManager.Router = Backbone.Router.extend({
  routes: {
    '': 'home',
    'employees': 'showEmployees',
    'employees/new': 'newEmployee',
    'employees/edit/:id': 'editEmployee',
  }
});

In the code above, whenever each of the routes on the left are visited, the name of the actions on the right are fired as events, so the router can listen on it like:

router.on('route:showEmployees', function() {
  ...
});

Create The App Logic

Let's go ahead and create the js/app.js file. This is the file that houses the functionalities that happens whenever each route is visited. Add the code below to it:

window.EmployeeManager = {
  Models: {},
  Collections: {},
  Views: {},

  start: function(data) {
    var employees = new EmployeeManager.Collections.Employees(data.employees),
        router = new EmployeeManager.Router();

    router.on('route:home', function() {
      router.navigate('employees', {
        trigger: true,
        replace: true
      });
    });

    router.on('route:showEmployees', function() {
      var employeesView = new EmployeeManager.Views.Employees({
        collection: employees
      });

      $('.main-container').html(employeesView.render().$el);
    });

    router.on('route:newEmployee', function() {
      var newEmployeeForm = new EmployeeManager.Views.EmployeeForm({
        model: new EmployeeManager.Models.Employee()
      });

      newEmployeeForm.on('form:submitted', function(attrs) {
        attrs.id = employees.isEmpty() ? 1 : (_.max(employees.pluck('id')) + 1);
        employees.add(attrs);
        router.navigate('employees', true);
      });

      $('.main-container').html(newEmployeeForm.render().$el);
    });

    router.on('route:editEmployee', function(id) {
      var employee = employees.get(id),
          editEmployeeForm;

      if (employee) {
        editEmployeeForm = new EmployeeManager.Views.EmployeeForm({
            model: employee
        });

        editEmployeeForm.on('form:submitted', function(attrs) {
          employee.set(attrs);
          router.navigate('employees', true);
        });

        $('.main-container').html(editEmployeeForm.render().$el);
      } else {
        router.navigate('employees', true);
      }
    });


    Backbone.history.start();
  }
};

Remember the EmployeeManager.start() function that was called in the index.html file? The function houses the events that the router listens on.

  • Whenever the user launches the app, the router listens on the home event, and triggers a redirect to the employees route.
  • Whenever the user hits the #employees route, it listens on the showEmployees event and renders the list of employees.
  • Whenever the user hits the #employee/new route, it listens on the newEmployee event, renders the form for adding new employees and handles the form submission.
  • Whenever the user hits the #employees/edit/:id route, it listens on the editEmployee event, and renders the edit form.

Backbone.history.start() is used to allow the use of hashbangs for the routes.

Run Your App

Now, launch your app. It should start up like so:

Show list of employees Launching the app

Create new employee Creating a new employee

Editing an employee details Editing an employee details

Aside: Auth0 Authentication with JavaScript

At Auth0, we make heavy use of full-stack JavaScript to help our customers to manage user identities including password resets, creating and provisioning, blocking and deleting users. We also created a serverless platform, called Auth0 Extend, that enables customers to run arbitrary JavaScript functions securely. Therefore, it must come as no surprise that using our identity management platform on JavaScript web apps is a piece of cake.

Auth0 offers a free tier to get started with modern authentication. Check it out!

Centralized Login Page Centralized Login Page

It's as easy as installing the auth0-js and jwt-decode node modules like so:

npm install jwt-decode auth0-js --save
const auth0 = new auth0.WebAuth({
    clientID: "YOUR-AUTH0-CLIENT-ID",
    domain: "YOUR-AUTH0-DOMAIN",
    scope: "openid email profile YOUR-ADDITIONAL-SCOPES",
    audience: "YOUR-API-AUDIENCES", // See https://auth0.com/docs/api-auth
    responseType: "token id_token",
    redirectUri: "http://localhost:9000" //YOUR-REDIRECT-URL
});

function logout() {
    localStorage.removeItem('id_token');
    localStorage.removeItem('access_token');
    window.location.href = "/";
}

function showProfileInfo(profile) {
    var btnLogin = document.getElementById('btn-login');
    var btnLogout = document.getElementById('btn-logout');
    var avatar = document.getElementById('avatar');
    document.getElementById('nickname').textContent = profile.nickname;
    btnLogin.style.display = "none";
    avatar.src = profile.picture;
    avatar.style.display = "block";
    btnLogout.style.display = "block";
}

function retrieveProfile() {
    var idToken = localStorage.getItem('id_token');
    if (idToken) {
        try {
            const profile = jwt_decode(idToken);
            showProfileInfo(profile);
        } catch (err) {
            alert('There was an error getting the profile: ' + err.message);
        }
    }
}

auth0.parseHash(window.location.hash, (err, result) => {
    if(err || !result) {
        // Handle error
        return;
    }

    // You can use the ID token to get user information in the frontend.
    localStorage.setItem('id_token', result.idToken);
    // You can use this token to interact with server-side APIs.
    localStorage.setItem('access_token', result.accessToken);
    retrieveProfile();
});

function afterLoad() {
    // buttons
    var btnLogin = document.getElementById('btn-login');
    var btnLogout = document.getElementById('btn-logout');

    btnLogin.addEventListener('click', function () {
        auth0.authorize();
    });

    btnLogout.addEventListener('click', function () {
        logout();
    });

    retrieveProfile();
}

window.addEventListener('load', afterLoad);

Get the full example using this code.

Go ahead and check out our quickstarts to learn how to implement authentication using different languages and frameworks in your apps.

Conclusion

BackboneJS is a library that can be used to build your user interfaces. Right now, it has not undergone updates in over a year. And many web applications that initially used it heavily have either moved away to new JS libraries or are currently rewriting their frontend to make use of another tool.

In addition, Auth0 can help secure your apps with more than just username-password authentication. It provides features like multifactor auth, anomaly detection, enterprise federation, single sign on (SSO), and more. Sign up today so you can focus on building features unique to your app.