This demo was written to illustrate how a server-side JavaScript solution could be applied to the TodosMVC application. I used Underscore.js, Backbone.js, Node.js, Express, Jade, Stylus, Mongoose and MongoDB.
A newer version using Socket.io and Redis for multi-user editing and record locking can be forked here on Github.
Technologies Used In This Demo
- Underscore.js – A utility-belt library for JavaScript without extending any of the built-in JavaScript objects.
- Backbone.js – Gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.
- jQuery – A fast, concise, library that simplifies how to traverse HTML documents, handle events, perform animations, and add AJAX.
- Node.js – Event-driven I/O server-side JavaScript environment based on V8.
- Express – High performance, high class web development for node.js.
- Jade – High performance template engine heavily influenced by Haml and implemented with JavaScript for node.js.
- Stylus – Expressive, dynamic, robust CSS for node.js
- Mongoose – A MongoDB object modeling tool designed to work in an asynchronous environment.
- MongoDB – A scalable, high-performance, open source NoSQL database.
Running the Demo
- Install node.js.
- Install MongoDB.
- Start the MongoDB server from a terminal window:
$ mongod - Change the working directory to the project root:
$ cd <path to todosmvc>/labs/architecture-examples/backbone_node_mongo/ - Install dependencies using the node package manger (npm).
$ sudo npm link - Start the Todos demo server from a different terminal window:
$ node app - Visit http://localhost:3000 in a web browser.
Interesting Parts
Here are a few interesting parts of the project that I wanted to highlight.
Create the Model Schema
Working with Mongoose is awesome. Simply define a schema that Mongoose will use to proxy the database. I know MongoDB is a schemaless document store but the schema is for Mongoose to use.
var mongoose = require('mongoose'),
TodoSchema = new mongoose.Schema({
title: { 'type': String, 'default': 'empty todo...' },
order: { 'type': Number },
done: { 'type': Boolean, 'default': false }
});
module.exports = mongoose.model('Todo', TodoSchema);
Setting Up the Routers
In order for Express to handle requests from the Backbone application routers must be set up to route RESTful URIs to handlers. Backbone has a default URI schema so the Express router is created to match it. The schema is as follow:
POST /todo //CREATE
GET /todo/:id //READ
PUT /todo/:id //UPDATE
DELETE /todo/:id //DELETE
For example the READ handler looks like this in my utils/crudUtils.js file.
(function (exports) {
"use strict";
// List, Create, Update and Delete omitted for brevity
// See full source on Github.
//------------------------------
// Read
//
// Generate and return a handler with an injected model
function getReadController(model) {
// Returning a closure so that the model can be stored
// in this function's environment
return function (req, res) {
// Look in MongoDB for the record
model.findById(req.params.id, function (err, result) {
if (err) {
res.send(409, err);
} else {
res.send(result);
}
});
};
}
// Export function so it's accessible outside of this module
exports.initRoutesForModel = function (options) {
var app = options.app
, model = options.model
, path
, pathWithId;
// We can only set up routers if we have a reference to the
// Express app we want to create them on and the model
// we want to create them for
if (!app || !model) {
return;
}
// Generate the RESTful URIs we expect to see from Backbone
path = options.path || '/' + model.modelName.toLowerCase();
pathWithId = path + '/:id';
app.get(path, getListController(model)); // List all Todos
app.post(path, getCreateController(model)); // CREATE
app.get(pathWithId, getReadController(model)); // READ
app.put(pathWithId, getUpdateController(model)); // UPDATE
app.del(pathWithId, getDeleteController(model)); // DELETE
};
}(exports));
Credit
- Jérôme Gravel-Niquet – Created original demo
- Addy Osmani – Cleanup, edits
- James O’Reilly – Added server-side tech from node.js to MongoDB