Backbone Tutorials
Beginner, Intermediate and Advanced Thomas Davis
This book is for sale at http://leanpub.com/backbonetutorials This version was published on 2012-10-23 This is a Leanpub book. Leanpub helps authors to self-publish in-progress ebooks. We call this idea Lean Publishing. To learn more about Lean Publishing, go to http://leanpub.com/manifesto. To learn more about Leanpub, go to http://leanpub.com.

©2012 Leanpub

Tweet This Book!
Please help Thomas Davis by spreading the word about this book on Twitter! The suggested hashtag for this book is #backbonetutorials. Find out what other people are saying about the book by clicking on this link to search for this hashtag on Twitter: https://twitter.com/search/#backbonetutorials

Contents
Why do you need Backbone.js? Why single page applications are the future . . . . . . . . . . . . . . . . . . . . . . . . . . . . So how does Backbone.js help? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Other frameworks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Contributors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . What is a view? The “el” property . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Loading a template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Listening for events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tips and Tricks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Relevant Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Contributors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . What is a model? Setting attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Getting attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Setting model defaults . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Manipulating model attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Interacting with the server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating a new model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Getting a model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Updating a model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Deleting a model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tips and Tricks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i i i i i ii ii iii iii iv v v vi vi vii vii vii ix ix x x xi xi

Listening for changes to the model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii

Contributors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xii What is a collection? xiii

Building a collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii

CONTENTS

What is a router? Dynamic Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

xv xv

Dynamic Routing Cont. “:params” and “*splats” . . . . . . . . . . . . . . . . . . . . . . . . . . xvi Relevant Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii Contributors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii Organizing your application using Modules (require.js) xviii

What is AMD? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xviii Why Require.js? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xviii Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xviii Example File Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix Bootstrapping your application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . What does the bootstrap look like? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xx xx

How should we lay out external scripts? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi A boiler plate module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi App.js Building our applications main module . . . . . . . . . . . . . . . . . . . . . . . . . . . xxii Modularizing a Backbone View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii Modularizing a Collection, Model and View . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiv Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv Relevant Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv Contributors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv Lightweight Infinite Scrolling using Twitter API xxvi

Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxvi The Twitter Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxvi Setting up the View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxvii The widget template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxviii Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxix Simple example - Node.js, Restify, MongoDb and Mongoose xxx

Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx The technologies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx Restify . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx MongoDb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx Mongoose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxx

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxiii Retrieving a list of messages . . . . . . . . xlv SEO for single page apps xlvi How does redirecting bots work? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xlviii Relevant Links . . . . xxxi Mongoose Schema . . . . . . . . . . . . . xlvi Implementation using Phantom. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xlv Relevant Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxvi Cross-domain Backbone. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxviii Checking session state at first load . . . . . . xxxvi Relevant Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .CONTENTS Building the server . . . . . . .js) . . xl Building a compatible server . . . . . . . . . . . . . . . . . . . . . . . xxxi MongoDb/Mongoose configuration . . xxxix Hooking up views to listen to changes in auth . . . . .js with sessions using CORS xxxvii Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xlix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxv Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxi Restify configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xlii Example node server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxviii An example Session model . . . . . xxxii Setting up the client (Backbone. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxi Setting up the routes . . . . . . . . . . . . . . . . . . . . . . . . . xxxvii Getting started . . . . . . . . . . . . . . . . . . xliii Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxxiii Saving a message . xlvi Redirecting bots . . . . . . . . . . . .

js as your framework. Google Goggles and electronic fridges etc. we now have mobile devices.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/ ⁴http://addyosmani. The problem is standard JavaScript libraries are great at what they do .net ³http://codebrief. all tied to concrete DOM elements.js? Building single-page web apps or complicated user interfaces will get extremely difficult by simply using jQuery¹ or MooTools².js enforces that communication to the server should be done entirely through a RESTful API.js help? Backbone is an incredibly small library for the amount of functionality and structure it gives you. You will with ease turn your application into a nested pile of jQuery callbacks. Of course you can always invent your own way of structuring your application but you miss out on the benefits of the open source community. So how does Backbone.com/todomvc/ ⁵https://github. tablet devices. If you read through some of the beginner tutorials the benefits will soon become self evident and due to Backbone. It is essentially MVC for the client and allows you to make your code modula r. try some of these resourceful links.js light nature you can incrementally include it in any current or future projects.github.com/FND i . please leave a comment below ¹http://jquery. Other frameworks If you are looking for comparisons to build your single page application. I shouldn’t need to explain why building something without any structure is a bad idea.Why do you need Backbone.Todo list implemented in the many different types of frontend frameworks⁴ Contributors • FND⁵ If you questions regarding why you should choose Backbone.com ²http://mootools. • A feature comparison of different frontend frameworks³ • Todo MVC . Why single page applications are the future Backbone. This is because the browser is no longer the only client. The web is currently trending such that all data/content will be exposed through an API.and without realizing it you can build an entire application without any formal structure.

1 2 3 4 5 6 7 8 9 10 SearchView = Backbone. Backbone.github. We will be using jQuery 1. 1 2 3 4 5 6 7 8 9 10 11 <div id="search_container"></div> <script type="text/javascript"> SearchView = Backbone.com/underscore/#template ⁷http://jquery.8.js documentation endorses jQuery. Any events we trigger must be in this element.template⁶. For the purposes of this demonstration. The “el” property The “el” property references the DOM object created in the browser. var search_view = new SearchView({ el: $("#search_container") }).js view has an “el” property.com/ ⁸http://mootools. but official Backbone. </script> Note: Keep in mind that this binds the container element.What is a view? Backbone views are used to reflect what your applications’ data models look like. we will be implementing a search box.net/ ⁹http://sizzlejs. A live example¹⁰ can be found on jsFiddle.").View the owner of the DOM element.2⁷ as our DOM manipulator. ⁶http://documentcloud."). Backbone. and if it not defined. It’s possible to use other libraries such as MooTools⁸ or Sizzle⁹. var search_view = new SearchView(). Every Backbone.com/ ¹⁰http://jsfiddle. They are also used to listen to events and react accordingly.extend({ initialize: function(){ alert("Alerts suck. // Consider it the constructor of the class.View events may not work with other libraries other than jQuery. } }).js will construct its own. which is an empty div element. Let us set our view’s “el” property to div#search_container. specifically Underscore. // The initialize function is always called when instantiating a Backbo\ ne View.View. effectively making Backbone. This tutorial will not be addressing how to bind models and collections to views but will focus on view functionality and how to use views with a JavaScript templating library.js’s _.View.net/tBS4X/1/ ii .extend({ initialize: function(){ alert("Alerts suck. } }).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script type="text/template" id="search_template"> <label>Search</label> <input type="text" id="search_input" /> <input type="button" id="search_button" value="Search" /> </script> <div id="search_container"></div> <script type="text/javascript"> SearchView = Backbone. </script> Tip: Place all your templates in a file and serve them from a CDN. which includes its own micro-templating solution.js is dependent on Underscore.js’s documentation¹¹ for more information.js.com/underscore/ . // Load the compiled HTML into the Backbone "el" this.render(). {}\ ). we use the “events” attribute of Backbone. Refer to Underscore.template( $("#search_template"). var search_view = new SearchView({ el: $("#search_container") }).html().extend({ initialize: function(){ this. Let us attach a “click” listener to our button.html( template ). Listening for events To attach a listener to our view. This ensures your users will always have your application cached.View.View. ¹¹http://documentcloud.What is a view? iii Loading a template Backbone. } }).github. The “render()” function will load our template into the view’s “el” property using jQuery. render: function(){ // Compile the template using underscore var template = _. Remember that event listeners can only be attached to child elements of the “el” property. }.$el. Let us implement a “render()” function and call it when the view is initialized.

</script> Tips and Tricks Using template variables 1 2 3 4 5 6 7 8 9 10 11 12 13 <script type="text/template" id="search_template"> <!-. }. doSearch: function( event ){ // Button clicked. render: function(){ var template = _.extend({ initialize: function(){ this.val() ). events: { "click input[type=button]": "doSearch" }.html( template ).View. .View. this. you can access the element that was clic\ ked with event.template( $("#search_template").$el.render(). {}\ ).currentTarget alert( "Search for " + $("#search_input"). var search_view = new SearchView({ el: $("#search_container") }). } }).extend({ initialize: function(){ this.html().Access template variables with <%= %> --> <label><%= search_label %></label> <input type="text" id="search_input" /> <input type="button" id="search_button" value="Search" /> </script> <div id="search_container"></div> <script type="text/javascript"> SearchView = Backbone.What is a view? iv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <script type="text/template" id="search_template"> <label>Search</label> <input type="text" id="search_input" /> <input type="button" id="search_button" value="Search" /> </script> <div id="search_container"></div> <script type="text/javascript"> SearchView = Backbone.render(). }.

currentTarget alert( "Search for " + $("#search_input").com/2011/02/05/backbone-views-and-templates.html( template ). </script> Relevant Links • This example implemented with google API¹² • This examples exact code on jsfiddle. var search_view = new SearchView({ el: $("#search_container") }).template( $("#search_template").html().net/thomas/dKK9Y/6/ ¹⁵https://github. you can access the element that was clic\ ked with event.What is a view? v }. va\ 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 riables ). // Load the compiled HTML into the Backbone "el" this.com/zaeleus ¹⁶https://github. render: function(){ //Pass variables in using Underscore.net/thomas/C9wew/4/ ¹⁴http://jsfiddle. } }).github.js Template var variables = { search_label: "My Search" }.val() ). doSearch: function( event ){ // Button clicked.html ¹³http://jsfiddle. }.$el.net¹³ • Another semi-complete example on jsFiddle¹⁴ Contributors • Michael Macias¹⁵ • Alex Lande¹⁶ ¹²http://thomasdavis. events: { "click input[type=button]": "doSearch" }. // Compile the template using underscore var template = _.com/lawnday .

validations. } }). 1 2 3 4 5 6 7 8 9 10 Person = Backbone. Models are the heart of any JavaScript application. var person = new Person. Setting attributes Now we want to pass some parameters when we create an instance of our model. computed properties. set afterwards. age: 67}).js have quite a clear definition of what they believe the model represents in backbone. age: 67}). 1 2 3 4 5 6 7 Person = Backbone. these operations are equivelent new Person().extend({ initialize: function(){ alert("Welcome to this world").extend({ initialize: function(){ alert("Welcome to this world"). So initialize() is triggered whenever you create a new instance of a model( models. and access control. Now that these models have attributes set we need to be able to retrieve them. var person = // or we can var person = person. name: "Thomas".js. So for the purpose of the tutorial let’s create a model.Model. ¹⁷http://en.Model.set().set({ new Person({ name: "Thomas". collections and views work the same way ). } }).org/wiki/Model%E2%80%93view%E2%80%93controller vi . containing the interactive data as well as a large part of the logic surrounding it: conversions.What is a model? Across the internet the definition of MVC¹⁷ is so diluted that it’s hard to tell what exactly your model should be doing. So passing a JavaScript object to our constructor is the same as calling model. The authors of backbone. You don’t have to include it in your model declaration but you will find yourself using it more often than not.wikipedia.

// 'Ryan' Manipulating model attributes Models can contain as many custom methods as you like to manipulate attributes. var age = person.What is a model? vii Getting attributes Using the model. 1 2 3 4 5 6 7 8 9 10 11 Person = Backbone.get("child"). var age = person. child: '' }. } }). // 67 var name = person.get("child"). This can easily be accomplished by setting a property name ‘defaults’ in your model declaration.get() method we can access model properties at anytime.Model. // 67 var name = person. age: 0.extend({ defaults: { name: 'Fetus'. child: 'Ryan'}).extend({ initialize: function(){ alert("Welcome to this world"). var person = new Person({ name: "Thomas". // "Thomas" var child = person. . } }). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Person = Backbone.get("age").get("name"). age: 67. // 'Ryan' Setting model defaults Sometimes you will want your model to contain default values. var person = new Person({ name: "Thomas".get("age"). initialize: function(){ alert("Welcome to this world"). By default all methods are public.get("name"). // "Thomas" var child = person.Model. child: 'Ryan'}). age: 67.

initialize: function(){ alert("Welcome to this world"). // 'John Resig' So we can implement methods to get/set and perform other calculations using attributes from our model at any time. . child: '' }. age: 0 }. this.Model. }. Listening for changes to the model Now onto one of the more useful parts of using a library such as backbone.What is a model? viii 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Person = Backbone. function(model){ var name = model.Model.extend({ defaults: { name: 'Fetus'. person. var person = new Person({ name: "Thomas".get("child"). var person = new Person({ name: "Thomas".’ to listen for changes to all attributes of the model. // This triggers a change and wil\ l alert() So we can bind the change listener to individual attributes or if we like simply ‘this. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Person = Backbone. person. }).extend({ defaults: { name: 'Fetus'. var child = person. age: 67}). } }). All attributes of a model can have listeners bound to them to detect changes to their values. age: 67. child: 'Ryan'}).on(“change”.adopt('John Resig'). // 'Stewie Griffin' alert("Changed my name to " + name ).on("change:name". In this case if the name of our “person” changes we will alert their new name.get("name"). adopt: function( newChildsName ){ this. initialize: function(){ alert("Welcome to this world").set({ child: newChildsName }). In our initialize function we are going to bind a function call everytime we change the value of our attribute.set({name: 'Stewie Griffin'}). } }). function(model){}). age: 0.

// Because we have not set a `id` the server will call // POST /user with a payload of {name:'Thomas'. email. defaults: { name: ''. The id attribute of a model identifies how to find it on the database usually mapping to the surrogate key¹⁸. Creating a new model If we wish to create a new user on the server then we will instantiate a new UserModel and call save. Our model definition shall thus look like. email: 'thomasalwyndavis@gmail. email: 'thomasalwyndavi\ s@gmail. name. If the id attribute of the model is null.org/wiki/Surrogate_key . For the purpose of this tutorial imagine that we have a mysql table called Users with the columns id. defaults: { name: ''. The server has implemented a RESTful URL /user which allows us to interact with it.js will send send of POST request to the server to the urlRoot.save(userDetails. 1 2 3 4 5 6 7 8 var UserModel = Backbone.wikipedia. // Notice that we haven't set an `id` var userDetails = { name: 'Thomas'. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var UserModel = Backbone. email: '' } }).Model. Backbone.Model. email: '' } }).com' }.extend({ urlRoot: '/user'.com'} // The server should save the data and return a response containing the\ new `id` user.extend({ urlRoot: '/user'.What is a model? ix Interacting with the server Models are used to represent data from your server and actions you perform on them will be translated to RESTful operations. var user = new Usermodel(). { ¹⁸http://en.

toJSON()). email: 'thomasalwyndavis@gmail. We will use the save api call which is intelligent and will send a PUT request instead of a POST request if an id is present(conforming to RESTful conventions) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Here we have set the `id` of the model var user = new Usermodel({ id: 1. ‘thomasalwyndavis@gmail. } }).What is a model? x 20 21 22 23 success: function (user) { alert(user.toJSON()). email: 'thomasalwynda\ vis@gmail. Backbone. { success: function (model) { alert(user. Backbone.js will automatically perform a get request to the urlRoot + ‘/id’ (conforming to RESTful conventions) 1 2 3 4 5 6 7 8 9 10 // Here we have set the `id` of the model var user = new Usermodel({id: 1}). We know that the id is 1 from the above example. If we instantiate a model with an id. } }) Updating a model Now that we model that exist on the server we can perform an update using a PUT request.toJSON()).com'}` user. // Let's change the name and update the server // Because there is `id` present.save({name: 'Davis'}. we can retrieve it from the server. } }) Our table should now have the values 1. ‘Thomas’.fetch({ success: function (user) { alert(user.js will fire // PUT /user/1 with a payload of `{name: 'Davis'. . // The fetch below will perform GET /user/1 // The server should return the id.com’ Getting a model Now that we have saved a new user model. name and email from the database user.com' }). name: 'Thomas'.

destroy will fire off a DELETE /user/id (conforming to RESTful conventions). */ Validate data before you set or save it 1 2 3 4 5 6 7 8 9 10 11 12 Person = Backbone. Backbone. email: 'thomasalwyndavis@gmail. // { name: "Thomas". this. // Because there is `id` present. age: 67} /* This simply returns a copy of the current attributes.js will fire // DELETE /user/1 user. Best practise would suggest that yo\ u use . alert it or forget it\ .What is a model? xi Deleting a model When a model has an id we know that it exist on the server. } }. // Backbone will throw an error validate: function( attributes ){ if( attributes. function(model.extend({ // If you return a string from the validate function. age: 67}).Model. } }). */ var attributes = person.set() to edit attributes of a model to take advantage of backbone li\ steners. Tips and Tricks Get all the current attributes 1 2 3 4 5 6 7 8 9 var person = new Person({ name: "Thomas".toJSON().bind("error". initialize: function(){ alert("Welcome to this world").attributes.destroy({ success: function () { alert('Destroyed').com' }). name: 'Thomas'.name != "Dr Manhatten" ){ return "You can't be negative years old". 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Here we have set the `id` of the model var user = new Usermodel({ id: 1. error){ // We have received an error. so if we wish to remove it from the server we can call destroy. log it. /* The line above gives a direct reference to the attributes and you sh\ ould be careful when playing with it.age < 0 && attributes. var attributes = person.

// Will trigger an alert outputting the error var person = new Person.What is a model? xii 13 14 15 16 17 18 19 20 21 22 23 24 25 :) alert( error ). person. age: -1 }).com/utkarshkukreti . person. age: -1 }).set({ name: "Dr Manhatten". }). // God have mercy on our souls Contributors • Utkarsh Kukreti¹⁹ ¹⁹https://github. var person = new Person.set({ name: "Mary Poppins". } }).

var Album = Backbone. Collection: ClassStudents • Model: Todo Item. Collection: Zoo Typically your collection will only use one type of model but models themselves are not limited to a type of collection. Collection: Gym Class • Model: Student. Collection: Art Class • Model: Student. Collection: Todo List • Model: Animals.extend({ initialize: function(){ console.What is a collection? Backbone collections are simply an ordered set of models²⁰. Collection: English Class Here is a generic Model/Collection example.log("Music is the answer"). • Model: Student. Building a collection Now we are going to populate a collection with some useful data. } }). 1 2 3 4 5 6 7 8 9 var Song = Backbone.extend({ model: Song }). • Model: Student.Collection. ²⁰http://backbonetutorials.Model.com/what-is-a-model xiii . Such that it can be used in situations such as.

log("Music is the answer").Collection. var song2 = new Song({ name: "Sexual Healing". // [song1. song2. artist: "OMC" }). song3] . song2. song3]). artist: "Marvin Gaye" })\ .Model.extend({ defaults: { name: "Not specified".extend({ model: Song }). var myAlbum = new Album([ song1. var song1 = new Song({ name: "How Bizarre". var Album = Backbone.models ). } }). var song3 = new Song({ name: "Talk It Over In Bed". artist: "OMC" }). console. initialize: function(){ console.What is a collection? xiv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var Song = Backbone. artist: "Not specified" }.log( myAlbum.

app_router.Router. Such that your URL would look like “http://example. All links in your application should target “#/action” or “#action”.history. // Initiate the router var app_router = new AppRouter. (Appending a forward slash after the hashtag looks a bit nicer e.g. Though a Backbone “router” is still very useful for any application/feature that needs URL routing/history capabilities.com/what-is-a-view xv . Defined routers should always contain at least one route and a function to map the particular route to.com/#a\ nything-here } }). http://example.com/#/posts/12”. function(actions) { alert(actions). In the traditional MVC sense they don’t necessarily fit the semantics and if you have read “What is a view?²¹” it will elaborate on this point. ²¹http://backbonetutorials. Also note that routes interpret anything after “#” tag in the URL.on('route:defaultRoute'. In the example below we are going to define a route that is always called. This example is implemented below. </script> Activate route Activate another route Notice the change in the url Dynamic Routing Most conventional frameworks allow you to define routes that contain a mix of static and dynamic route parameters. Once this route was activated you would want to access the id given in the URL string.extend({ routes: { "*actions": "defaultRoute" // matches http://example.start(). For example you might want to retrieve a post with a variable id with a friendly URL string.What is a router? Backbone routers are used for routing your applications URL’s when using hash tags(#).com/#/user/help) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script> var AppRouter = Backbone. }) // Start Backbone history a necessary step for bookmarkable URL's Backbone.

function (actions) { alert( actions ).extend({ routes: { "posts/:id": "getPost". Any “*splats” or “:params” in route definitions are passed as arguments (in respective order) to the associated function. (If this is confusing please post a comment and I will try articulate it better) Here are some examples of using “:params” and “*splats” . A route defined as “/:route/:action” will pass 2 variables (“route” and “action”) to the callback function.history.What is a router? xvi 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script> var AppRouter = Backbone. }).start(). "*actions": "defaultRoute" // Backbone will try match the r\ oute above first } }).on('route:defaultRoute'. app_router. Then there are “splats” which match any number of URL components. First there are “:params” which match any URL components between slashes. // Instantiate the router var app_router = new AppRouter.Router. function (id) { // Note the variable in the route definition being passed in he\ re alert( "Get post number " + id ). “:params” and “*splats” Backbone uses two styles of variables when implementing routes. Note that due to the nature of a “ splat” it will always be the last variable in your URL as it will match any and all components. app_router. </script> Post 120 Post 130 Notice the change in the url Dynamic Routing Cont. }). // Start Backbone history a necessary step for bookmarkable URL's Backbone.on('route:getPost'.

github. // dashboard_graph }).What is a router? xvii 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 routes: { "posts/:id": "getPost". action ){ alert(route + "_" + action). If you need to implement hash tags with SEO in mind. Routes are quite powerful and in an ideal world your application should never contain too many. function( id ){ alert(id).on('route:getPost'. // 121 }).on('route:downloadFile'.(Backbone 0.5 rename from Controller to Router) • Paul Irish²⁶ ²²http://seo. do a google search for “google seo hashbangs”.com/#/download/user/images/hey. // <a href="http://example.apiengine. function( route.on('route:loadView'.io ²³http://documentcloud. Also check out Seo Server²² Remember to do a pull request for any errors you come across.com/2011/02/07/making-a-restful-ajax-app.com .html ²⁵http://schistad. Relevant Links • Backbone.gif">Down\ load</a> ":route/:action": "loadView". app_router. app_router. app_router.gif }). // user/images/hey.js official router documentation²³ • Using routes and understanding the hash tag²⁴ Contributors • Herman Schistad²⁵ . function( path ){ alert(path). // <a href="http://example.github.com/backbone/#Router ²⁴http://thomasdavis. // <a href="http://example.com/#/posts/121">Example</a> "download/*path": "downloadFile".com/#/dashboard/graph">Load Route/Action\ View</a> }.info ²⁶http://paulirish.

html ³¹http://dojotoolkit.js) Unfortunately Backbone.js? a.com/amdjs/amdjs-api/wiki/AMD ²⁸https://github. This tutorial will get you started on combining Backbone. What is AMD? Asynchronous Module Definitions²⁸ designed to load modular code asynchronously in the browser and server.6. seeing it as the future of modular JavaScript development. I highly recommend using AMD for application development Quick Overview • Modular • Scalable • Compiles well(see r.js. This tutorial will use Require.js has a great community and it is growing rapidly. This was quite a different decision to other JavaScript MVC frameworks who were more in favor of setting a development philosophy.html ³²http://tagneto.org ³⁰http://requirejs.com/amdjs/amdjs-api/wiki/AMD ²⁹http://requirejs. Example Codebase³³ ²⁷https://github. leaving many developers in the dark regarding how to load scripts and lay out their development environments.blogspot.6 converted fully to AMD³¹ ) Why Require.js specification. Many script loaders have built their implementations around AMD.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/modular-backbone xviii . It is actually a fork of the Common. Getting started To easily understand this tutorial you should jump straight into the example code base.js³⁰ ) • Market Adoption( Dojo 1. Require. Hopefully this tutorial will allow you to build a much more robust project with great separation of concerns between design and code. He is a leading expert in script loading and a contributer to the AMD specification.com/ ³³https://github.js with AMD²⁷ (Asynchronous Module Definitions). James Burke³² the author is married to Require.js²⁹ to implement a modular and organized Backbone.Organizing your application using Modules (require.org/reference-guide/releasenotes/1.org/docs/optimization.js and always responds to user feedback.js does not tell you how to organize your code.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /* File Structure ├── imgs ├── css │ └── style.js │ ├── collections │ │ ├── users.min.min.js │ │ └── projects.Organizing your application using Modules (require.js ³⁴http://backbonetutorials.js │ ├── views │ │ ├── projects │ │ │ ├── list.com/examples/modular-backbone .js │ ├── models │ │ ├── users.js │ │ └── users │ │ ├── list.js │ │ └── underscore │ │ │ ├── underscore.js │ │ └── edit.js │ │ └── projects. Collections and Models are categorized into folders kind of like an ORM. In the example below views and templates are mirrored in file structure.js │ │ │ └── edit.css ├── templates │ ├── projects │ │ ├── list.html │ └── users │ ├── list.min.html │ │ └── edit. Example File Structure There are many different ways to lay out your files and I believe it is actually dependent on the size and type of the project.js │ │ ├── backbone │ │ │ ├── backbone.js) xix Example Demo³⁴ The tutorial is only loosely coupled with the example and you will find the example to be more comprehensive. The example isn’t super fleshed out but should give you a vague idea.(Example Request: How to do nested views).html ├── js │ ├── libs │ │ ├── jquery │ │ │ ├── jquery.html │ └── edit. If you would like to see how a particular use case would be implemented please visit the GitHub page and create an issue.

Underscore and Backbone.js to load the script located at “js/main. In the example below we configure Require. .js // Bootstrap ├── order. We should setup any useful containers that might be used by our Backbone views. Bootstrapping your application Using Require.js) xx 33 34 35 36 37 38 │ │ │ │ │ └── ├── router. You can serve this off your server and then the rest of your site off a CDN ensuring that everything that can be cached.Load the script "js/main.js ├── main.js //Require.js" as our entry point --> <script data-main="js/main" src="js/libs/require/require.js and loading initially important dependencies.js plugin index.js” 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!doctype html> <html lang="en"> <head> <title>Jackie Chan</title> <!-. Note: The data-main attribute on our single script tag tells Require.Organizing your application using Modules (require. It automatically appends the “.js”.js ├── app.js we define a single entry point on our index page. (You can also now serve the index file off the CDN using Cloudfront) What does the bootstrap look like? Our bootstrap file will be responsible for configuring Require.js"></script\ > </head> <body> <div id="container"> <div id="menu"></div> <div id="content"></div> </div> </body> </html> You should most always end up with quite a light weight index file.html */ To continue you must really understand what we are aiming towards as described in the introduction.js to create a shortcut alias to commonly used scripts such as jQuery.js plugin └── text. will be.js //Require.

let’s quickly look over boiler plate code that will be reused quite often.config({ paths: { jquery: 'libs/jquery/jquery'. underscore: 'libs/underscore/underscore'.js) xxi Unfortunately Backbone.js” in my application root so I can copy it when I need to.js isn’t AMD enabled so I downloaded the community managed repository and patched it on amdjs³⁵. We also request a module called “app”. }).js allows us to configure shortcut alias // There usage will become more apparent further along in the tutorial.js”. Note: Modules are loaded relatively to the boot strap and always append with “. A boiler plate module So before we start developing our application. require([ // Load our app module and pass it to our definition function 'app'. We have a heavy dependency on jQuery. require.initialize(). How should we lay out external scripts? Any modules we develop for our application using AMD/Require. unfortunately this libraries are loaded synchronously and also depend on each other existing in the global namespace. Due to this inconvenience the bootstrap is not as intuitive as it could be. backbone: 'libs/backbone/backbone' } }). Underscore and Backbone.Organizing your application using Modules (require.com/amdjs . function(App){ // The "app" dependency is passed in as "App" App.js” which is in the same directory as the bootstrap. For convenience sake I generally keep a “boilerplate. this will contain the entirety of our application logic. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // Filename: main.js will be asynchronously loaded.js // Require. ]. ³⁵https://github. So the module “app” will load “app. Hopefully if the AMD specification takes off these libraries will add code to allow themselves to be loaded asynchronously.

The router will then load the correct dependencies depending on the current URL. 'views/projects/list'. // Filename: router. // What we return here will be used by other modules }). // Request router. Router){ var initialize = function(){ // Pass in our Router module and call it's initialize function Router.js define([ 'jquery'. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // Filename: app. // lib/jquery/jquery 'underscore'. // lib/underscore/underscore 'backbone' // lib/backbone/backbone ].Organizing your application using Modules (require.js Building our applications main module Our applications main module should always remain light weight. _. 'views/users/list' . The first argument of the define function is our dependency array. }).initialize().js define([ // These are path alias that we configured in our bootstrap 'jquery'. in the future we can pass in any modules we like. Backbone){ // Above we have passed in jQuery. function($.js ]. 'router'. Underscore and Backbone // They will not be accessible in the global scope return {}. This tutorial only covers setting up a Backbone Router and initializing it in our main module. 'underscore'. App. 'underscore'. Backbone. 'backbone'.js define([ 'jquery'. _. function($. 'backbone'.js) xxii 1 2 3 4 5 6 7 8 9 10 11 12 13 //Filename: boilerplate. } return { initialize: initialize }.

// As above. Modularizing a Backbone View Backbone views usually interact with the DOM.log('No route:'. var initialize = function(){ var app_router = new AppRouter. . }). projectListView. Session.on('defaultAction'.extend({ routes: { // Define some URL routes '/projects': 'showProjects'. return { initialize: initialize }. UserListView){ var AppRouter = Backbone.Router. ProjectListView. Backbone.render(). app_router. _.Organizing your application using Modules (require.js text! plug-in. }).on('showUsers'. // Default '*actions": 'defaultAction' } }). userListView. Backbone.render(). }).on('showProjects'. }). Using our new modular system we can load in JavaScript templates using the Require. call render on our loaded module // 'views/users/list' app_router. function(){ // Call render on the module we loaded in via the dependency arra\ y // 'views/projects/list' var projectListView = new ProjectListView(). actions). function($. '/users': 'showUsers'. app_router.js) xxiii 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 ]. lets just log what the URL was console. function(actions){ // We have no matching route. function(){ var userListView = new UserListView().history.start(). }.

we are loaded raw text // which will be used as our views primary template 'text!templates/project/list. 'underscore'. render: function(){ // Using Underscore we can compile our template with data var data = {}. Backbone. Collection and View which is a typical scenario when building a Backbone. // Using the Require.html' ]. Backbone. // Return the model for the module return ProjectModel. }).View.js offers great benefits when doing this. We will set the “model” attribute of our collection to the loaded module.Organizing your application using Modules (require. }).Model. 'backbone'.template( projectListTemplate.extend({ defaults: { name: "Harry Potter" } }). projectListTemplate){ var ProjectListView = Backbone. var compiledTemplate = _. Backbone){ var ProjectModel = Backbone. JavaScript templating allows us to separate the design from the application logic by placing all our HTML in the templates folder.extend({ el: $('#container').js application. _. Modularizing a Collection. } }). data ).append( compiledTemplate ). // Append our compiled template to this Views "el" this. function($. Model and View Now we put it altogether by chaining up a Model. function(_.$el. 'backbone' ].js text! plugin. Now that we have a model. our collection module can depend on it. // Our module now returns our view return ProjectListView. . First we will define our model 1 2 3 4 5 6 7 8 9 10 11 12 13 // Filename: models/project define([ 'underscore'.js) xxiv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // Filename: views/project/list define([ 'jquery'.

comments or GitHub! Relevant Links • Organizing Your Backbone. you can pass raw attributes objects (and arrays) to add. // Pull in the Model module from above 'models/project' ]. projectsListTemplate){ var ProjectListView = Backbone.html(compiledTemplate). } }).extend({ model: ProjectModel }). ‘text!templates/projects/list ].add({ name: “Ginger Kid”}).com/dzejkej . this.js Application With Modules³⁶ Contributors • Jakub Kozisek³⁷ (created modular-backbone-updated containing updated libs with AMD support) ³⁶http://weblog. ProjectModel){ var ProjectCollection = Backbone.js) xxv Collection. // Filename: views/projects/list define([ ‘jquery’. Backbone.View. 1 2 3 4 5 6 7 8 9 10 11 12 13 // Filename: collections/projects define([ 'underscore'. }).template( projectsListTemplate.models } ). and the attributes will be converted into a model of the proper type.collection. Conclusion Looking forward to feedback so I can turn this post and example into quality references on building modular JavaScript applications. }). // You don't usually return a collection instantiated return ProjectCollection. If defined. function(_. 'backbone'. // Compile the template using Underscores micro-templating var compiledTemplate = _.model: Override this property to specify the model class that the collection contains.Collection.collection = new ProjectsCollection(). // Pull in the Collection module from above ‘collections/projects’. create. function($.bocoup. Now we can simply depend on our collection in our view and pass it to our JavaScript template.$el. Get in touch with me on twitter.extend({ el: $(“#container”). // Returning instantiated views can be quite useful for having “state” return ProjectListView. ‘underscore’. Backbone.collection. _. ‘backbone’. this. { projects: this. initialize: function(){ this.Organizing your application using Modules (require. and reset. ProjectsCollection.com/organizing-your-backbone-js-application-with-modules ³⁷https://github.

query + \ '&page=' + this.js define([ 'jquery'.wikipedia.json?q=' + this.com/search.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/infinite-scroll ⁴⁰http://backbonetutorials. So in our collection definition we can override the Backbone.Collection. In the collection definition below we have set some defaults which can be overridden at any point. function($.js will re-sync with the server to bring down the next page of results.js at the correct property parse: function(resp. }. The first thing to note is that we have to append ‘&callback?’ to allow cross domain Ajax calls which is a feature of jsonp⁴¹.page + '&callback=?' }. Though this is a problem for Backbone.js because a Collection expects to be populated with an array of objects. xhr) { return resp.twitter. ³⁸http://backbonetutorials. Example Demo³⁸ Example Source³⁹ Note: This tutorial will use AMD⁴⁰ for modularity. page: 1. Backbone){ var Tweets = Backbone. The Twitter Collection Twitter offers a jsonp API for browsing tweets. 'underscore'. 'backbone' ].Lightweight Infinite Scrolling using Twitter API Getting started In this example we are going to build a widget that pulls in tweets and when the user scrolls to the bottom of the widget Backbone.com/organizing-backbone-using-modules ⁴¹http://en.results. _.com/examples/infinite-scroll/ ³⁹https://github. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // collections/twitter.org/wiki/JSONP xxvi . Twitter’s search API actually returns a whole bunch of meta information alongside the results.extend({ url: function () { return 'http://search.js default parse function to instead choose the correct property to populate the collection. // Because twitter doesn't return an array of models by default we \ need // to point Backbone. Using the ‘q’ and ‘page’ query parameters we can find the results we are after.

render: function () { this. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // views/twitter/widget. }.extend({ el: '. }. 'collections/twitter'. xhr) { this. 'vm'. loadResults will be responsible for calling fetch on our Twitter collection. }. TwitterCollection.twitter-widget'.completed_in return resp. 'backbone'.results. loadResults: function () { var that = this.isLoading = false.twitterwidget’.View. Backbone. 1 2 3 4 parse: function(resp.twitterCollection = new TwitterCollection(). initialize: function () { // isLoading is a useful flag to make sure we don't send off more\ than // one request at a time this. return Tweets.isLoading = true. 'text!templates/twitter/list. On success we will append the latest results to our widget using our template. If the current scrollTop is at the bottom then we simply increment the Twitter collections current page property and call loadResults again.g. _. Setting up the View The first thing to do is to load our Twitter collection and template into the widget module.js define([ 'jquery'. 'underscore'. Note: Feel free to attach the meta information returned by Twitter to the collection itself e. }). this.js tutorials' }).loadResults().Lightweight Infinite Scrolling using Twitter API xxvii 19 20 21 22 23 query: 'backbone. Vm. TwitterListTemplate)\ { var TwitterWidget = Backbone. We should attach our collection to our view in our initialize function.js events will listen for scroll on the current el of the view which is ‘. .completed_in = resp. // we are starting a new load of results so set isLoading to true this. function($. Our Backbone.html' ].

el.scrollHeight ) { this.el. // Load next page this. checkScroll: function () { var triggerPoint = 100. }).page += 1.el. } }).scrollTop + this.Lightweight Infinite Scrolling using Twitter API xxviii 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 // fetch is Backbone.twitterCollection.clientHeight\ + triggerPoint > this. {tweets: tw\ eets.twitterCollection.isLoading = false. . } } }).template(TwitterListTemplate. return TwitterWidget. // Now we have finished loading set isLoading back to false that. }.loadResults().append(_.models. _:_})).el).fetch({ success: function (tweets) { // Once the results are returned lets populate our template $(that. // This will simply listen for scroll events on the current el events: { 'scroll': 'checkScroll' }. // 100px from the bottom if( !this.js native function for calling and parsing t\ he collection url this.isLoading && this. Note: triggerPoint will allow you to set an offset where the user has to scroll to before loading the next page The widget template Our view above passes into our underscore template the variable tweets which we can simply iterate over with using underscore’s each method.

get('text') %></li> <% }).Lightweight Infinite Scrolling using Twitter API xxix 1 2 3 4 5 6 7 8 <!-.com/examples/infinite-scroll/ ⁴³https://github. There are caveats to using infinite scroll in UI/UX so make sure to only use it when applicable. %> </ul> Conclusion This is a very lightweight but robust infinite scroll example. Example Demo⁴² Example Source⁴³ ⁴²http://backbonetutorials.templates/twitter/list.each(tweets. function (tweet) { %> <li><%= tweet.html --> <ul class="tweets"> <% _.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/infinite-scroll .

Personal note: I love using JavaScript as my only language for the entire application (FrontEnd/BackEnd/API/Database). scalable network applications.js parts of this tutorial will be using techniques described in “Organizing your application using Modules⁴⁴ to construct a simple guestbook. open source NoSQL database. non-blocking I/O model that makes it lightweight and efficient. Node. Getting started To easily understand this tutorial you should jump straight into the example code base.js “Node. the Backbone. Restify.js.” ⁴⁴http://backbonetutorials.Simple example .com/organizing-backbone-using-modules/ ⁴⁵https://github.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/nodejs-mongodb-mongoose-restify ⁴⁶http://backbonetutorials.js Models) to MongoDb and retrieving a list(Backbone.” Mongoose “Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Node. Restify is still in early development but is essentially just an extension of Express. high-performance. perfect for data-intensive real-time applications that run across distributed devices.” Restify “Restify is a node.” MongoDb “MongoDB (from “humongous”) is a scalable. It borrows heavily from express (intentionally) as that is more or less the de facto API for writing web applications on top of node. MongoDb and Mongoose Before I start. Example Codebase⁴⁵ Example Demo⁴⁶ This tutorial will assist you in saving data(Backbone.js uses an event-driven.js module built specifically to enable you to build correct REST web services.Node. So for anyone needing more stability you can easily just substitute Express in.js Collections) of them back.js.com/examples/nodejs-mongodb-mongoose-restify/ xxx . The technologies This stack is great for rapid prototyping and highly intuitive.js is a platform built on Chrome’s JavaScript runtime for easily building fast.

js ⁴⁸https://github. db = mongoose.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/js/models/message.Simple example . Schema = mongoose.js ⁵⁰http://mongoosejs. Your config should never be public as it contains your credentials. Restify will be in control of handling our restful endpoints and returning the appropriate JSON.js Model⁴⁷ and Collection⁴⁸ definitions to match your server address.creds.Schema.js example which can be executed by running node server. var config = require('.bodyParser()).com/docs/model-definition. So for this repository I have added config. If you use this example in your own applications make sure to update the Backbone.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/js/collections/messages.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/config-sample. Note: bodyParser() takes care of turning your request data into a JavaScript object on the server automatically.js. 1 2 3 var restify = require('restify')./config').connect(config. Restify configuration The first thing to do is require the Restify module. MongoDb/Mongoose configuration We simply want to require the MongoDb module and pass it a MongoDb authentication URI e.mongoose_auth).gitignore but added in a sample config⁴⁹.Node. server. mongodb://username:server@mongoserver:10059/somecollection The code below presupposes you have another file in the same directory called config.js.createServer().js. 1 2 3 4 var mongoose = require('mongoose/').use(restify. Mongoose Schema Mongoose introduces a concept of model/schema⁵⁰ enforcing types which allow for easier input validation etc ⁴⁷https://github. MongoDb and Mongoose xxxi Building the server In the example repository there is a server.js to my . js ⁴⁹https://github. var server = restify.g.html . Restify.

}). fill it up and save it to Mongodb var message = new Message().message = req. message.g. } . we attach them to either GET/POST/PUT/DELETE on a particular restful endpoint e.execFind(function (arr.Node. Restify allows you to configure different routes and their associated callbacks. // . After we have created our function definitions. message. next) { res. "*").send(req.save(function () { res.header("Access-Control-Allow-Headers". "*"). }).send(data).header("Access-Control-Allow-Headers". message. res.find(). res.body). "X-Requested-With").data) { res. // Use the schema to register a model with MongoDb mongoose. One for saving new messages and one for retrieving all messages. -1).model('Message'). will return all results // the `-1` in . MongoDb and Mongoose xxxii 1 2 3 4 5 6 7 8 // Create a schema for our data var MessageSchema = new Schema({ message: String. var Message = mongoose.Simple example . In the code below we define two routes.sort() means descending order Message.js. Setting up the routes Just like in Backbone. Note: Message can now be used for all things CRUD related. date: Date }).message. GET /messages 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // This function is responsible for returning all entries for the Messa\ ge model function getMessages(req.sort('date'. next) { // Resitify currently has a bug which doesn't allow you to set defaul\ t headers // This headers comply with CORS and allow us to server our response \ to any origin res. res. MessageSchema).model('Message'. Restify.header("Access-Control-Allow-Origin".params. res. // Create a new message model.find() without any arguments.header("Access-Control-Allow-Origin". "X-Requested-With"). } function postMessage(req.date = new Date().

js.nodejitsu.com/messages⁵² Note: Again you must remember to change the Model⁵³ and Collection⁵⁴ definitions to match your server address. postMessage).post('/messages'.get('/messages'.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/js/collections/messages.js.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/server.Simple example .js ⁵²http://backbonetutorials.Node. This wraps up the server side of things.js • collections/messages. js ⁵⁵http://backboneboilerplate. Let us create a Backbone Model that has the correct URL for our restful interface.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/templates/guestbook/ form.js • models/message. if you follow the example⁵¹ then you should see something like http://backbonetutorials. Setting up the client (Backbone.com⁵⁵ to set up the example page. getMessages).html . ⁵¹https://github. server. MongoDb and Mongoose xxxiii 31 32 33 // Set up our routes and start the server server. Restify. 1 2 <textarea class="message"></textarea> <button class="post-message">Post Message</button> This template gets inserted into the DOM by views/guestbook/form.com ⁵⁶https://github.js) I’ve actually used the latest copy of http://backboneboilerplate. this Backbone view also handles the interaction of the form and the posting of the new data.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/nodejs-mongodb-mongoose-restify/js/models/message.js • views/guestbook/form.com/messages ⁵³https://github. • views/dashboard/page.js • views/guestbook/list.js ⁵⁴https://github.js • templates/guestbook/ Saving a message First of all we want to setup a template⁵⁶ for showing our form that creates new messages. The important files you will want to check out are.nodejitsu.

Note: trigger is from Backbone Events.message'). . function($.Simple example . { success: function () { that. }). We are setting the date of the POST on the server so there is no need to pass it up.el).Node. } }). events: { 'click . postMessage: function() { var that = this. _.trigger('postMessage'). I binded a listener to this view in views/dashboard/page. MongoDb and Mongoose xxxiv 1 2 3 4 5 6 7 8 9 define([ 'underscore'.js. Restify. 'backbone'. render: function () { $(this. 'underscore'. Backbone. var message = new MessageModel(). We can see how we require our predefined model for messages and also our form template. return Message. 'text!templates/guestbook/form. }).extend({ url: 'http://localhost:8080/messages' }). }.guestbook-form-container'. return GuestbookForm. function(_. Backbone) { var Message = Backbone. } }). the list is re-rendered.View.Model. guestbookFormTemplate){ var GuestbookForm = Backbone.html(guestbookFormTemplate).html' ].val()}.save({ message: $('.js so when a new message is submitted. message. MessageModel.extend({ el: '.post-message': 'postMessage' }. 'models/message'. 'backbone' ]. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 define([ 'jquery'.

MessageModel){ var Messages = Backbone. var messages = new MessagesCollection(). 'models/message' ]. So we need to define a collection with the appropriate url to fetch this data down. return Messages. _.template(guestbookListTemplate. guestbookListTemplate){ var GuestbookList = Backbone.el). MongoDb and Mongoose xxxv Retrieving a list of messages We setup a route on our server to generate a list of all available messages at GET /messages.Simple example .Collection. The template file should iterate over messages.View.fetch({ success: function(messages) { $(that. function($.Node. 'underscore'. 'collections/messages'. 'underscore'. Backbone. 'text!templates/guestbook/list.js to require the collection and trigger a fetch. } }). Once the fetch is complete we want to render our returned data to a template and insert it into the DOM.guestbook-list-container'. messages. .html(_.extend({ model: MessageModel.html' ]. return GuestbookList.js. }).models which is an array and print out a HTML fragment for each model. render: function () { var that = this. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 define([ 'jquery'. 'backbone'. {messages: \ messages. MessagesCollection.extend({ el: '. } }). Restify.models. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 define([ 'jquery'. 'backbone'. _:_})). // Generally best practise to bring down a Mod\ el/Schema for your collection url: 'http://localhost:8080/messages' }). _. function($. Now that we have a collection to use we can setup our views/list. }). Backbone.

function(message) { %> <p><%= message. MongoDb and Mongoose xxxvi 1 2 3 4 5 6 <% _.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/nodejs-mongodb-mongoose-restify ⁵⁸http://backbonetutorials. Restify. Conclusion Example Codebase⁵⁷ Example Demo⁵⁸ In this example you should really be using relative URL’s in your collections/models and instead setting a baseUrl in a config file or by placing your index.Node. %> This actually sums up everything you need to know to implement this simple example.get('message') %></p> <em><%= message.com/examples/nodejs-mongodb-mongoose-restify/ ⁵⁹http://weblog.com/organizing-your-backbone-js-application-with-modules .js.each(messages.get('date') %></em> <% }).bocoup. I have of recent used the Joyent.html file on the restful server.js Application With Modules⁵⁹ ⁵⁷https://github. MongoDbHq stack after they have now partnered up and I have nothing but good things to say. comments or GitHub! Relevant Links Organizing Your Backbone. Highly recommend you check it out! As always I hope I made this tutorial easy to follow! Get in touch with me on twitter.com On a personal note. This example is hosted on GitHub therefore we had to include the absolute URL to the server which is hosted on nodejitsu.Simple example . Nodejitsu.

• Develop only one API on the server. . • This separation enforces that the API be built robustly.org/ ⁶¹http://easyxdm.html xxxvii . testing now becomes easier and more controlled. • As a front-end developer you can host the client anywhere.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/cross-domain/server. documented.js ⁶³http://blog.js⁶²) • Protect again JSON padding⁶³ ⁶⁰http://enable-cors. CORS headers aren’t supported by Opera and IE 6/7.js with sessions using CORS ** This tutorial is a proof of concept and needs to be checked for security flaws ** This tutorial will teach you how to completely separate the server and client allowing for developers to work with freedom in their respective areas. Cross-Origin Resource Sharing (CORS) is a specification that enables a truly open access across domain-boundaries. collaboratively and versioned. Security • Don’t allow GET request to change data.net/wp/ ⁶²https://github.enable-cors. On a personal note. • Due to the separation of concerns. your front-end could be outsourced or built by a in-house team. only retrieve. • Whitelist your allowed domains (see server.Cross-domain Backbone.opensecurityresearch. I consider this development practice highly desirable and encourage others to think of the possible benefits but the security still needs to be proved. Though it is do-able using easyXDM⁶¹ • Security is somewhat addressed but maybe a more thorough security expert can chime in.org⁶⁰ Some benefits include • The client and back end exist independently regardless of where they are each hosted and built. ** Cons of this tutorial ** • This tutorial doesn’t explain how to perform this with cross browser support.com/2012/02/json-csrf-with-parameter-padding.

We will give it // A call back when we know what the auth status is Session. }.extend({ el: '. Example Codebase⁶⁴ Example Demo⁶⁵ This tutorial focuses on building a flexible Session model to control session state in your application.url = 'http://localhost:8000' + options. We will jump into our Session model next. Checking session state at first load Before starting any routes.Cross-domain Backbone. jqXHR ) { // Your server goes below //options. Backbone. originalOptions.getAuth has checked the server. 'text!templates/layout. We will simply wrap our Backbone. Vm. 'backbone'.url\ .html' ]. we should really know whether the user is authenticated.com' + options.url = 'http://cross-domain.url.start in a callback that executes after Session.com/examples/cross-domain/ .el). therefore // when the user refreshes the page we should // really know if they're authed. 'models/session'. 'vm'.getAuth(function () { Backbone. function($. Host the codebase on a simple HTTP server such that the domain is localhost with port 80 hidden.start(). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 define([ 'jquery'. layoutTemplate){ var AppView = Backbone.container'.nodejitsu.ajaxPrefilter( function( options.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/cross-domain ⁶⁵http://backbonetutorials. options. $(this.history.html(layoutTemplate). Events.js with sessions using CORS xxxviii Getting started To easily understand this tutorial you should jump straight into the example code base. }). 'events'. Session.history. initialize: function () { $. // This is the entry point to your app.View. }) ⁶⁴https://github. 'underscore'. This will allow us to load the appropriate views. render: function () { var that = this. _.

save(creds. 'backbone' ]. This is where we specify what server we want the application to hit. login: function(creds) { // Do a POST to /session and send the serialized form creds this. The model simply has a login.ajaxPrefilter( function( options. (This may not be best practice but it’s highly convenient) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // views/app. that.Cross-domain Backbone. return AppView. An example Session model This is a very light weight Session model which handles most situations. originalOptions.extend({ urlRoot: '/session'. . // If we have a csrf token send it through with the next reques\ t if(typeof that.setRequestHeader('X-CSRF-Token'. } }).js with sessions using CORS xxxix 31 32 33 34 } }). Again we have hooked into jQuery ajaxPrefilter to allow for csrf tokens and also telling jQuery to send cookies with the withCredentials property. }). Because we return this AMD module instantiated using the new keyword.js define([ 'underscore'. function(_. each view can simply bind to change:auth on the Session model and react accordingly. initialize: function () { var that = this. }. logout and check function.get('_csrf') !== 'undefined') { jqXHR.Model. jqXHR ) { options. The model relies heavily on it’s auth property. // Hook into jquery // Use withCredentials to send the server cookies // The server must allow this through response headers $. Throughout your application.xhrFields = { withCredentials: true }. Backbone) { var SessionModel = Backbone. Read through the code and comments below. then it will keep state throughout the page.get('_csrf')). }. Note: We have used jQuery ajaxPrefilter to hook into all AJAX requests before they are executed. { success: function () {} }).

// Set auth to false to trigger a change:auth event // The server also returns a new csrf token so that // the user can relogin without refreshing the page that. _csrf: resp. getAuth: function(callback) { // getAuth is wrapped around our router // before we start any routers let us see if the user is valid this. }).fetch({ success: callback }).set({auth: false. Note: This session model is missing one useful feature. this.Cross-domain Backbone. Hooking up views to listen to changes in auth Now that we have a Session model. . When creating the view we use on to bind a listener to the auth attribute of our model.clear() model. }. } }).js with sessions using CORS xl 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 logout: function() { // Do a DELETE to /session and clear the clientside data var that = this. resp) { model. Everytime it changes we will re-render the view which will conditionally load a template depending on the value of Session. To do this. return new SessionModel(). let’s hook up our login/logout view to listen to changes in auth. } }).destroy({ success: function (model.get('auth'). If a user looses auth when navigating your application then the application should set {auth: false} on this model.id = null._csrf}). in the ajaxPrefilter edit outgoing success functions to check if the server response was {auth: false} and then call the original success() function.

html'. return false. Session. 'underscore'.js define([ 'jquery'.html(_. 'disabled').page'. _. 'text!templates/example/login. events: { 'submit form. login: function (ev) { // Disable the button $('[type=submit]'.currentTarget).logout': 'logout' }. }).on('change:auth'.get('auth')){ this. exampleLogou\ tTemplate){ var ExamplePage = Backbone.val('Logging in'). logout: function (ev) { // Disable the button $(ev. 'models/session'.View.logout(). 'text!templates/example/logout.$el.currentTarget). } .js with sessions using CORS xli 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 // models/session. Session. render: function () { // Simply choose which template to choose depending on // our Session models auth attribute if(Session. // On form submission 'click . function (session) { that.serializeObject(). function($. ev.html(exampleLoginTemplate).render(). exampleLoginTemplate. Session. // Bind to the Session auth attribute so we // make our view act recordingly when auth changes Session. } }.login': 'login'. } else { this. Backbone.html' ].Cross-domain Backbone.template(exampleLogoutTemplate.currentTarget).attr('disa\ bled'.$el. 'backbone'.login(creds).attr('disabled'. initialize: function () { var that = this. {username: Sess\ ion.get('username')})). }. 'disable\ d'). // Serialize the form into an object using a jQuery plgin var creds = $(ev.extend({ el: '. }.text('Logging out').

Cross-domain Backbone. • You must use withCredentials supplied by jQuery .html --> <p>Hello. Time to logout?</p> <button class="logout">Logout</button> This wraps up setting up the client.js • You should wrap your applications entry pointer (router in this example) in a check auth function .app.js and a modified csrf.js with sessions using CORS xlii 50 51 52 }).js library. make sure your app.js.js • You must send your request with csrf tokens for security .js .session.session. <%= username %>.app. }).templates/example/login. express. The server has to do a few things.templates/example/logout. regardless it will be converted to JSON and posted to the server like any normal Backbone model. there are some notable points to make sure this technique works. Again. • Allow CORS request • Implement csrf protection • Allow jQuery to send credentials • Set a whitelist of allowed domains ⁶⁶https://github. An example server.js points at the correct server.js Building a compatible server This tutorial uses node.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/cross-domain/js/views/app. Note: . return ExamplePage.js⁶⁶ in the demo folder.js file exist in the examples/cross-domain folder.js to start the server. Here are the templates we are using for our login view 1 2 3 4 5 6 7 8 9 10 <!-.js • You must point your application at the right server . When inside the folder simply type npm install -d to install the dependencies and then node server.serializeObject is not a native jQuery function and I have included it as app. creds can be an object of any variation of inputs.html --> <form class="login"> <label for="">Username</label> <input name="username" type="text" required autofocus> <input type="submit" id="submit" value="Login"> </form> <!-.

// Custom csrf library var csrf = require('.com'./csrf'). xrequested-with. accept so our server must allow them. Be sure to read this Mozilla documentation⁶⁷ on the above. • The browser might send out a pre-flight request to verify that it can talk to the server. It should be relatively easy to see how would translate into other frameworks and languages. 'http://localhost' ⁶⁷http://hacks.Simply returns if auth is true or false. We have told the server that on each request it should check the csrf token and check if the origin domain is white-listed.Logout .org/2009/07/cross-site-xmlhttprequest-with-cors/ .Sets the session username and returns a csrf token for the user to use • DELETE /session .createServer().Login . that are pseudo-restful. • POST /session .Checks Auth . if true then also returns some session details 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var express = require('express'). This server has 3 endpoints. next) { // Added other domains you want the server to give access to // WARNING . • When sending withCredentials you must set correct response header Access-Control-AllowCredentials: true. res.Be careful with what origins you give access to var allowedHost = [ 'http://backbonetutorials. Also as a security policy browsers do not allow Access-Control-AllowOrigin to be set to *.Destroys the session and regenerates a new csrf token if the user wants to re-login • GET /session . var allowCrossDomain = function(req. var app = express. If so we edit each request to contain the appropriate headers.js with sessions using CORS xliii • Configure the correct response headers To save you sometime here are some gotchas. var connect = require('connect').configure runs the specified libraries against every request. • jQuery ajax will trigger the browser to send these headers to enforce security origin.Cross-domain Backbone. so in the example below we use an of white listed domains. So the origin of the request has to be known and trusted. Example node server This server below implements everything we have talked about so far.mozilla. app. The server must return 200 OK on these pre-flight request.

bodyParser()). Content-MD5. username: req.session.send({auth: false}).username = req. true).origin) res.get('/session'. id: req.header('Access-Control-Allow-Methods'.del('/session/:id'.id. app.header('Access-Control-Allow-Credentials'.headers. } }). next){ // Logout by clearing the session req.use(csrf. app. function(req.session. Accept-Version.session.session.origin) !== -1) { res. _csrf: req. res.use(express. X-Requested\ -With. res){ // This checks the current users auth // It runs before Backbones router is started // we should return a csrf token for Backbone to use if(typeof req.username. }). if(allowedHost.cookieParser()). _csrf: req. function(req.use(express. id: req. Content-Length. } else { res. res. function(req.check). 'X-CSRF-Token. app.session.send({auth: true._csrf}).send({auth: true.js with sessions using CORS xliv 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 ]. } else { res. app.indexOf(req. next().use(express. D\ ate.header('Access-Control-Allow-Origin'. res. res. app. }).use(allowCrossDomain). } } app.DELETE.POST.body. req.header('Access-Control-Allow-Headers'.OPTIO\ NS').session.session.regenerate(function(err){ // Generate a new csrf token so the user can login again . username: req. res){ // Login // Here you would pull down your user credentials and match them up // to the request req.session.configure(function() { app.id. app.usernam\ e}). X-Api-Version').headers.usern\ ame. 'GET.post('/session'. Accept._csrf}). app.session. Content-Type.session({ secret: 'thomasdavislovessalmon' })).PUT.username !== 'undefined'){ res.send({auth: false.Cross-domain Backbone.

listen(8000).org/2009/07/cross-site-xmlhttprequest-with-cors/ ⁷¹http://www.com/examples/cross-domain/ ⁷⁰http://hacks. Note: I wrote a custom csrf module for this which can be found in the example directory. _csrf: req.send({auth: false. It’s based of connects and uses the crypto library. A powerful API will let you do application iterations with ease.session. function () { res. app.aspx .com/thomasdavis/backbonetutorials/tree/gh-pages/examples/cross-domain ⁶⁹http://backbonetutorials.w3.Cross-domain Backbone. Again.js cross domain! I cannot get passed the spam filter on HackerNews so feel free to submit this tutorial Example Codebase⁶⁸ Example Demo⁶⁹ Relevant Links • cross-site xmlhttprequest with CORS⁷⁰ • Cross-Origin Resource Sharing⁷¹ • Using CORS with All (Modern) Browsers⁷² ⁶⁸https://github. connect. }). }). Enjoy using Backbone.js with sessions using CORS xlv 64 65 66 67 68 69 70 71 72 // This is pretty hacky.kendoui.generate(req.mozilla. I didn’t spend much time on it but other traditional csrf modules won’t work because they aren’t exactly built for this implementation technique.org/TR/cors/ ⁷²http://www.com/blogs/teamblog/posts/11-10-04/using_cors_with_all_modern_browsers.csrf isn't built for rest // I will probably release a restful csrf module csrf. Conclusion This approach really hammers in the need for a well documented and designed API. res. }). it would be great for some more analysis of the security model._csrf}).

the first element of the // array is our phantomjs script and the second element is our url var phantom = require('child_process'). var getContent = function(url. It is on the shoulders of the search engines to conform and they should not dictate how the web is stored and accessed.SEO for single page apps This tutorial will show you how to index your application on search engines. Here is an image made by Google depicting the setup. In 2009 Google released the idea of escaped fragments⁷³.js and phantom. headless seo Implementation using Phantom. ⁷³http://googlewebmastercentral. ['phantom-s\ erver.blogspot. one which is the web server and the other is a phantomjs script that renders the page.js onto a box. We are going to setup a node. Then we will redirect bots to this server to retrieve the correct content.js Phantom. callback) { var content = ''. The idea simply stating that if a search engine should come across your JavaScript application then you have the permission to redirect the search engine to another URL that serves the fully rendered version of the page (The current search engines cannot execute much JavaScript (Some people speculate that Google Chrome was born of Google Search wishing to successfully render every web page to retrieve ajaxed content)). Which speeds up development for the ever increasing array of clients.stdout.js⁷⁴ is a headless webkit browser. // Here we spawn a phantom.js server that given a URL.html ⁷⁴http://phantomjs. How does redirecting bots work? Using modern headless browsers. There are two files.js'.au/2009/10/proposal-for-making-ajax-crawlable. var app = express().js // Express is our web server that can handle request var express = require('express'). it will fully render the page content. url]). Then start up this server below.setEncoding('utf8').com. phantom.org/ xlvi . As the author I believe that servers should be completely independent of the client in the age of API’s.js process. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // web. we can easily return the fully rendered content per request by redirecting bots using our web servers configuration.spawn('phantomjs'. You will need to install node.

getContent(url. function(data) { content += data. } else { // once our phantom. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var page = require('webpage'). We don’t return the content until the page is fully rendered. requestIds[requestIds.id) !== -1) { lastReceived = new Date(). } }. responseCount++.create().id) === -1) { requestIds.indexOf(response. } }).toString(). var system = require('system'). var var var var var lastReceived = new Date(). page. let's call out call back // which outputs the contents to the page callback(content).id).on('exit'.*)/. The script below is phantom-server. }).params[0]. }. respond). responseCount = 0. phantom. function(code) { if (code !== 0) { console.onResourceReceived = function (response) { if(requestIds. app.SEO for single page apps xlvii 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // Our phantom. requestCount = 0.log('We have an error').on('data'.send(content).js script exits.onResourceRequested = function (request) { if(requestIds.js script is simply logging the output and // we access it here through stdout phantom. } app.get(/(.indexOf(response.indexOf(request.push(request.getTime(). We hook into the resources listener to do this. page.listen(3000).js and will be in charge of fully rendering the content. requestIds = [].stdout. . function (content) { res.getTime(). startTime = new Date(). }).getTime().id)] = null.headers['x-forwarded-host'] + req. res) { // Because we use [P] in htaccess we have access to this header url = 'http://' + req. var respond = function (req.

such as user agent to redirect other search engines we wish to be indexed on. ⁷⁵http://seo.getTime() .log(page. It is also called Seo Server⁷⁶. Redirecting bots If you are using apache we can edit out . function () {}).args[1]. You will most likely have to use both.apiengine.htaccess such that Google requests are proxied to our middle man phantom. console.xml’s for your JavaScript applications.io ⁷⁶http://seoserver.getTime() .content).open(system. <meta name="fragment" content="!"> or using #! URLs in our links.SEO for single page apps xlviii 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 requestCount++.*) http://webserver:3000/%1? [P] We could also include other RewriteCond. Though Google won’t use _escaped_fragment_ unless we tell it to by either including a meta tag. I have released an open source npm package called seo server⁷⁵ for anyone wanting to jump straight in. which takes care of the whole process and even generates sitemap. Make sure you include #! on your URLs when using the fetch tool. 1 2 3 RewriteEngine on RewriteCond %{QUERY_STRING} ^_escaped_fragment_=(.exit(). Once we have this server up and running we just redirect bots to the server in our client’s web server configuration.js server. And also via request I have setup a service currently in beta. } } // Let us check to see if the page is finished rendering var checkCompleteInterval = setInterval(checkComplete.io .startTime > 5000) { clearInterval(checkCompleteInterval). } }.*)$ RewriteRule (.apiengine. 1). (currently in beta) This has been tested with Google Webmasters fetch tool. phantom. // Open the page page. var checkComplete = function () { // We don't allow it to take longer than 5 seconds but // don't return until all requests are finished if((new Date().lastReceived > 300 && requestCount === res\ ponseCount) || new Date().

SEO for single page apps xlix Relevant Links • Open source node.apiengine.io .js Seo Server⁷⁷ ⁷⁷http://seo.

Sign up to vote on this title
UsefulNot useful