Backbone.js Sync and the Server
Your models behave beautifully right up until someone refreshes the page, and then every edit is gone. Data that only lives in the browser is not really saved; it needs a server. Backbone handles that with one quiet function you rarely call by name, Backbone.sync, and four methods you call constantly. Learn how those connect to plain HTTP and persistence stops feeling like a separate subject.
This guide is the client side of the story. For the server that answers these requests, see the companion tutorial on building an API with Node, Restify, MongoDB, and Mongoose. Here we focus on how a Backbone Model and Collection turn into requests and back into data.
What you'll learn
One function behind everything: Backbone.sync
Every time a model reads or writes, it routes through a single function called Backbone.sync. You almost never call it directly; fetch, save, and destroy call it for you. Its whole job is to translate a persistence action into an HTTP request, using jQuery's ajax under the hood by default.
How CRUD maps to HTTP verbs
The mapping is fixed and worth memorising: create becomes POST, read becomes GET, update becomes PUT, and delete becomes DELETE. That is the entire contract between Backbone and a conventional REST API. Because it is just HTTP, any backend that speaks REST will work, regardless of language.
Telling Backbone where data lives
Before Backbone can send a request, it needs an address. A collection gets a url; a standalone model gets a urlRoot, and it appends its own id to that root to build the full path.
How a model builds its own URL
var User = Backbone.Model.extend({ urlRoot: '/api/users' });
var u = new User({ id: 42 });
console.log(u.url());
// => /api/users/42
If the model belongs to a collection that already has a url, it can skip urlRoot entirely and inherit the path. Either way, the rule is the same: the collection address points at the list, and a single record lives at that address plus its id.
Reading with fetch()
fetch is read. Call it on a model to load one record, or on a collection to load many, and Backbone issues a GET, then merges the JSON it receives into your objects. Because it is a network call, the data is not there on the next line; you react to its arrival, not its request.
Fetching a collection from the server
var Users = Backbone.Collection.extend({
model: User,
url: '/api/users'
});
var users = new Users();
users.fetch(); // GET /api/users, then fills the collection with models
On success the collection populates and fires events you can render against. The natural pattern is the one from the lifecycle guide: a view listens for the collection updating and re-renders once the data lands.
Writing with save(): create versus update
A single method, save, handles both creating and updating, and it decides which by asking whether the model is new. That one check is the whole reason you do not need separate create and update calls.
Why save() sometimes POSTs and sometimes PUTs
var u = new User({ name: 'Ada' });
u.isNew(); // true -> save() will POST to create
u.save(); // POST /api/users
// once the server has assigned an id:
u.save({ name: 'Grace' }); // PUT /api/users/42 to update
A model with no id is new, so save sends a POST; once the server returns an id, the next save sends a PUT. By default Backbone updates the model locally right away; pass the wait option when you would rather hold the change until the server confirms it.
Deleting, and reacting to results
destroy sends a DELETE and, if the model is in a collection, removes it from that collection too. Around all of these calls Backbone fires lifecycle events, which is the clean way to show loading and error states without tangling that logic into the request itself.
The request, sync, and error events
u.on('request', function () { console.log('saving...'); });
u.on('sync', function () { console.log('saved'); });
u.on('error', function () { console.log('failed'); });
u.destroy(); // DELETE /api/users/42, then fires 'sync' on success
request fires when a call starts, sync when it succeeds, and error when it fails, exactly the hooks a spinner and an error message want. These are ordinary Backbone events, so everything from the events guide applies, including using listenTo from a view. The calls also return the jqXHR, so u.fetch().then(onOk, onErr) works when you prefer promises.
Reshaping the response with parse()
Real APIs rarely return exactly the flat object a model expects. When the shapes differ, parse is the seam where you translate the server's response into your model's attributes, so the rest of your code never has to know the API was awkward.
When your API does not match Backbone's defaults
// server replies: { "data": { "id": 42, "name": "Ada" } }
var User = Backbone.Model.extend({
urlRoot: '/api/users',
parse: function (response) {
return response.data; // unwrap to the real attributes
}
});
Here parse unwraps a record nested under a data key. A collection has its own parse for pulling an array out of a larger payload. And when a backend is not REST at all, you can replace Backbone.sync entirely, pointing every read and write at WebSockets or local storage instead.
That is persistence in Backbone: one sync function, four methods, a fixed map to HTTP, events for the in-between states, and parse for when reality disagrees with the defaults. With data flowing both ways, the next foundations topic, application structure, is about wiring these pieces together without the whole thing turning to spaghetti.
Frequently Asked Questions
What HTTP method does Backbone use for save()?
It depends on whether the model is new. If the model has no id, isNew is true and save sends a POST to create it. If the model already has an id, save sends a PUT to update it. Passing the patch option makes it send a PATCH with only the changed fields.
How does a Backbone model know its URL?
A model builds its URL from urlRoot plus its id, for example urlRoot /api/users and id 42 gives /api/users/42. If the model belongs to a collection, it can inherit the collection's url instead of setting urlRoot itself.
Does fetch() return a promise?
Yes. fetch, save, and destroy return the jqXHR object, which is thenable, so you can chain then with success and error handlers. You can also pass success and error callbacks in the options, or listen for the request, sync, and error events.
How do I handle a server response that does not match my model?
Override parse on the model or collection to transform the raw response into the attributes Backbone expects, for example unwrapping a response that nests the record under a data key. For a backend that is not REST at all, override Backbone.sync.
Read next: Backbone.js Application Structure, the next foundations guide, or build the matching backend with Node, Restify, MongoDB, and Mongoose.
Connect the front and back
Sync is the bridge to your API. The complete guide shows how Models, Views, Routers, and Collections work as one application.
Explore the Backbone Guide →