Backbone Sync with localStorage — Client-Side Persistence
In this tutorial, you will learn about Backbone Sync with localStorage. We cover key concepts, practical examples, and best practices to help you master this topic.
Backbone.sync is the persistence layer that handles CRUD operations. By overriding it, you can store data in localStorage instead of a server. This enables offline-capable applications, prototypes that work without a backend, and client-side Caching.
What You'll Learn
You'll learn how Backbone.sync works, how to override it for localStorage, handle CRUD operations locally, and build applications that work offline.
Why It Matters
Client-side persistence lets applications work without a server. Security tools that scan local files can store results in localStorage. Offline-first applications sync when connectivity is available.
Real-World Use
A portable network scanner tool stores scan results in localStorage. Users run scans offline on air-gapped networks. Results persist across page refreshes and sync when connectivity is restored.
flowchart LR
A[model.save] --> B[Backbone.sync]
B --> C{Adapter?}
C -->|Default| D[REST API]
C -->|localStorage| E[localStorage]
E --> F[JSON.parse/stringify]
F --> G[Browser Storage]
How Backbone.sync Works
Backbone.sync determines the HTTP method based on the CRUD operation and calls the appropriate Adapter.
// Default Backbone.sync maps:
// create -> POST
// read -> GET
// update -> PUT
// patch -> PATCH
// delete -> DELETE
// You can see what method would be used
Backbone.sync = function(method, model, options) {
console.log('Sync called:', method, model.url ? model.url() : 'no url');
// Default implementation would send HTTP request here
};
var Task = Backbone.Model.extend({ urlRoot: '/api/tasks' });
var task = new Task({ title: 'Test' });
task.save(); // would call sync('create', ...)
localStorage Adapter
Override Backbone.sync to read and write from localStorage.
// localStorage adapter
var Store = function(name) {
this.name = name;
this.data = JSON.parse(localStorage.getItem(name) || '{}');
};
Store.prototype.save = function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
};
Store.prototype.findAll = function() {
return Object.keys(this.data).map(function(id) {
return this.data[id];
}.bind(this));
};
Store.prototype.findById = function(id) {
return this.data[id];
};
Store.prototype.create = function(model) {
var id = Date.now().toString();
model.id = id;
this.data[id] = model;
this.save();
return model;
};
Store.prototype.update = function(model) {
this.data[model.id] = model;
this.save();
return model;
};
Store.prototype.destroy = function(model) {
delete this.data[model.id];
this.save();
return model;
};
Using the Store with Backbone.sync
var taskStore = new Store('tasks');
Backbone.sync = function(method, model, options) {
var resp;
var store = taskStore;
switch (method) {
case 'read':
resp = model.id ? store.findById(model.id) : store.findAll();
break;
case 'create':
resp = store.create(model.toJSON());
break;
case 'update':
case 'patch':
resp = store.update(model.toJSON());
break;
case 'delete':
resp = store.destroy(model.toJSON());
break;
}
if (resp) {
options.success(resp);
} else {
options.error('Record not found');
}
};
var Task = Backbone.Model.extend({
defaults: { title: '', completed: false }
});
var TaskList = Backbone.Collection.extend({ model: Task });
// Create and save
var task = new Task({ title: 'Learn localStorage sync' });
task.save(null, {
success: function() {
console.log('Saved with ID:', task.id);
console.log('Stored in localStorage');
}
});
// Fetch all
var tasks = new TaskList();
tasks.fetch({
success: function() {
console.log('Loaded', tasks.length, 'tasks from localStorage');
tasks.each(function(t) {
console.log('-', t.get('title'));
});
}
});
Expected output:
Saved with ID: 1719572000000
Stored in localStorage
Loaded 1 tasks from localStorage
- Learn localStorage sync
Complete localStorage Backbone App
A working todo application using localStorage persistence.
// Store setup
var todoStore = new Store('todos');
Backbone.sync = function(method, model, options) {
var store = todoStore;
var resp;
switch (method) {
case 'read':
resp = model.id ? store.findById(model.id) : store.findAll();
break;
case 'create':
resp = store.create(model.toJSON());
break;
case 'update':
case 'patch':
resp = store.update(model.toJSON());
break;
case 'delete':
resp = store.destroy(model.toJSON());
break;
}
options.success(resp);
};
// Models and Collections
var Todo = Backbone.Model.extend({
defaults: { title: '', completed: false },
toggle: function() {
this.set('completed', !this.get('completed'));
}
});
var TodoList = Backbone.Collection.extend({
model: Todo,
completed: function() {
return this.where({ completed: true });
},
remaining: function() {
return this.where({ completed: false });
}
});
// Usage
var todos = new TodoList();
todos.fetch();
todos.create({ title: 'Build localStorage adapter' });
todos.create({ title: 'Write tests' });
todos.create({ title: 'Deploy app' });
console.log('Total:', todos.length);
console.log('Remaining:', todos.remaining().length);
var first = todos.at(0);
first.toggle();
first.save();
console.log('Completed:', todos.completed().length);
Expected output:
Total: 3
Remaining: 3
Completed: 1
Hybrid Approach: localStorage with Server Sync
Combine localStorage for offline and server sync when online.
var HybridSync = function(method, model, options) {
var localStore = new Store('app_cache');
var resp;
// Always save locally first
switch (method) {
case 'read':
resp = model.id ? localStore.findById(model.id) : localStore.findAll();
break;
case 'create':
resp = localStore.create(model.toJSON());
break;
case 'update':
case 'patch':
resp = localStore.update(model.toJSON());
break;
case 'delete':
resp = localStore.destroy(model.toJSON());
break;
}
// If online, also sync with server
if (navigator.onLine) {
var serverSync = Backbone.$.ajax({
url: model.url(),
type: method === 'create' ? 'POST' :
method === 'update' ? 'PUT' :
method === 'delete' ? 'DELETE' : 'GET',
data: JSON.stringify(model.toJSON()),
contentType: 'application/json',
success: function(serverResp) {
console.log('Server sync successful');
},
error: function() {
console.log('Server sync failed — data safe in localStorage');
}
});
} else {
console.log('Offline — saved to localStorage only');
}
options.success(resp);
};
Backbone.sync = HybridSync;
Common Mistakes
- Storing model instances directly in localStorage. localStorage stores strings. Always serialize with
JSON.stringify()and deserialize withJSON.parse(). - Not generating unique IDs for localStorage records. Without unique IDs,
fetch()andget()return wrong data. Use timestamps or UUIDs. - Exceeding localStorage quota (5MB). localStorage has a 5MB limit per origin. For larger data, use IndexedDB.
- Assuming localStorage is available. Some browsers restrict localStorage in private mode. Always wrap with a try-catch.
- Not clearing old localStorage data. Stale data accumulates. Provide a clear mechanism or use versioned store names.
Practice Questions
- What does Backbone.sync do by default?
- How do you override Backbone.sync for localStorage?
- What is the storage limit of localStorage?
- How can you combine localStorage and server persistence?
- Challenge: Create a localStorage adapter that supports namespacing (multiple collections in the same app). Implement
findWhereandwhereat the store level. Test with CRUD operations on two different collections.
FAQ
Mini Project
Build a complete Notes app using Backbone with localStorage persistence. Notes have title, content, tags, createdAt, and updatedAt. Support CRUD operations. Add a search feature that filters notes by title or tags. All data persists across page refreshes.
What's Next
Now that you understand localStorage sync, learn Backbone REST Persistence for server-side data. Then explore Backbone Marionette Introduction for larger application patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro