Javascript – How to sync data with remote database in case of offline-first applications

javascriptofflineprogressive-web-appsrestservice-worker

  • I am building a "TODO" application which uses Service Workers to cache the request's responses and in case a user is offline, the cached data is displayed to the user.
  • The Server exposes an REST-ful endpoint which has POST, PUT, DELETE and GET endpoints exposed for the resources.
  • Considering that when the user is offline and submitting a TODO item, I save that to local IndexedDB, but I can't send this POST request for the server since there is no network connection. The same is true for the PUT, DELETE requests where a user updates or deletes an existing TODO item

Questions

  • What patterns are in use to sync the pending requests with the REST-ful Server when the connection is back online?

Best Solution

What patterns are in use to sync the pending requests with the REST-ful Server when the connection is back online?

Background Sync API will be suitable for this scenario. It enables web applications to synchronize data in the background. With this, it can defer actions until the user has a reliable connection, ensuring that whatever the user wants to send is actually sent. Even if the user navigates away or closes the browser, the action is performed and you could notify the user if desired.

Since you're saving to IndexDB, you could register for a sync event when the user add, delete or update a TODO item

function addTodo(todo) {
  return addToIndeDB(todo).then(() => {
    // Wait for the scoped service worker registration to get a
    // service worker with an active state
    return navigator.serviceWorker.ready;
  }).then(reg => {
    return reg.sync.register('add-todo');
  }).then(() => {
    console.log('Sync registered!');
  }).catch(() => {
    console.log('Sync registration failed :(');
  });
}

You've registered a sync event of type add-todo which you'll listen for in the service-worker and then when you get this event, you retrieve the data from the IndexDB and do a POST to your Restful API.

self.addEventListener('sync', event => {
if (event.tag == 'add-todo') {
      event.waitUntil(
      getTodo().then(todos => {
        // Post the messages to the server
        return fetch('/add', {
          method: 'POST',
          body: JSON.stringify(todos),
          headers: { 'Content-Type': 'application/json' }
        }).then(() => {
          // Success!
        });
      })
      })
    );
   }
});

This is just an example of how you could achieve it using Background Sync. Note that you'll have to handle conflict resolution on the server.

You could use PouchDB on the client and Couchbase or CouchDB on the server. With PouchDB on the client, you can save data on the client and set it to automatically sync/replicate the data whenever the user is online. When the database synchronizes and there are conflicting changes, CouchDB will detect this and will flag the affected document with the special attribute "_conflicts":true. It determines which one it'll use as the latest revision, and save the others as the previous revision of that record. It does not attempt to merge the conflicting revision. It is up to you to dictate how the merging should be done in your application. It's not so different from Couchbase too. See the links below for more on Conflict Resolution.

I've used pouchDB and couchbase/couchdb/IBM cloudant but I've done that through Hoodie It has user authentication out-of-the box, handles conflict management, and a few more. Think of it like your backend. In your TODO application, Hoodie will be a great fit. I've written something on how to use Hoodie, see links Below: