Skip to content

Backbone Sync with localStorage — Client-Side Persistence

DodaTech Updated 2026-06-28 5 min read

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

  1. Storing model instances directly in localStorage. localStorage stores strings. Always serialize with JSON.stringify() and deserialize with JSON.parse().
  2. Not generating unique IDs for localStorage records. Without unique IDs, fetch() and get() return wrong data. Use timestamps or UUIDs.
  3. Exceeding localStorage quota (5MB). localStorage has a 5MB limit per origin. For larger data, use IndexedDB.
  4. Assuming localStorage is available. Some browsers restrict localStorage in private mode. Always wrap with a try-catch.
  5. Not clearing old localStorage data. Stale data accumulates. Provide a clear mechanism or use versioned store names.

Practice Questions

  1. What does Backbone.sync do by default?
  2. How do you override Backbone.sync for localStorage?
  3. What is the storage limit of localStorage?
  4. How can you combine localStorage and server persistence?
  5. Challenge: Create a localStorage adapter that supports namespacing (multiple collections in the same app). Implement findWhere and where at the store level. Test with CRUD operations on two different collections.

FAQ

Is localStorage persistent across browser sessions?

Yes. Data persists until explicitly deleted or the cache is cleared.

Can I use localStorage in private browsing mode?

Some browsers disable localStorage in private mode. Always check with a try-catch.

How do I clear all data from a store?

Clear the object and call localStorage.removeItem(storeName).

Is there a size limit for individual items?

The 5MB limit applies to the entire origin. Individual items can be up to ~5MB minus other items.

Can I use this for sensitive data?

No. localStorage is not encrypted. Never store passwords, tokens, or sensitive data.

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