Using PouchDB and Couchbase in an Offline-First Application

Published Mar 08, 2017Last updated Mar 09, 2017
Using PouchDB and Couchbase in an Offline-First Application

An offline-first app is an app that works — without error — when there is no network connection or when the connection is flaky. An offline-first app can provide a better, faster user experience — both offline and online — by storing content and data locally and then synchronising them with the cloud when a reliable network connection is available. Offline-first apps are designed and developed in a way where a lack of connection is not considered an error. Rather, apps will work well offline, with all important features or functionalities available in this mode, and then progressively enhance when online with a stable connection. In this post, we'll look at offline data storage and synchronisation by building a web phonebook app to store contacts.

Building the sample app

Our sample app will be a web app that'll be built with Bootstrap, jQuery, PouchDB, Hoodie store-client plugin for PouchDB, and Couchbase Sync Gateway. To start with, we'll layout the page that will include a form to enter the contact's name, email and phone, and also display list of saved contacts.

Laying out the page

  • Create a folder for the application.
  • Download bootstrap and jQuery.
  • Unzip the files and put them in a new folder called asset.
  • Add a new file called index.html to your root folder and copy the below snippet to it.
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>My Hoodie App</title>
    <link rel="stylesheet" href="assets/bootstrap/bootstrap.min.css">
</head>

<body>

    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#"> Phonebook</a>
            </div>
        </div>
    </nav>
    <div class="container">

        <div class="row">
            <div class="col-md-10">
                <h2>Add new contact </h2>
                <hr />
                <form id="contactForm" class="form-horizontal">
                    <div class="form-group">
                        <label for="name" class="col-sm-2 control-label">Name</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="name" placeholder="Name">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="mobile" class="col-sm-2 control-label">Mobile</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="mobile" placeholder="Mobile">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="email" class="col-sm-2 control-label">Email</label>
                        <div class="col-sm-10">
                            <input type="email" class="form-control" id="email" placeholder="Email">
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-sm-offset-2 col-sm-10">
                            <button type="submit" class="btn btn-default">Save Contact</button>
                        </div>
                    </div>
                </form>
                <hr />
            </div>
        </div>

        <div class="row">
            <div class="col-md-10">
                <h2>Contact List</h2>
                <hr />
                <table id="contactList" class="table table-bordered">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Mobile</th>
                            <th>Email</th>
                            <th></th>
                        </tr>
                    </thead>
                    <tbody>

                    </tbody>
                </table>
            </div>
        </div>


        <script src="assets/jquery-2.1.0.min.js"></script>
        <script src="assets/bootstrap/bootstrap.min.js"></script>
</body>

</html>

What we have is a page with a form to enter contact information and save it. At the bottom the list of saved contacts, you'll see this display: page layout

Offline-First Data Access/Storage

For our sample, we'll use PouchDB — a JavaScript client-side database API modelled after the CouchDB API — for storing and retrieving data locally. PouchDB abstracts away the different browser supported database and their different programming interface. With this, the data will be stored locally, after which it'll be synced to the server when there is connection.

We will also use hoodie store-client, a PouchDB plugin for offline data persistence and sync. I prefer to work with this plugin because of the API available for working with PouchDB. It automatically adds createdDate and updatedDate when I add new data. I'll add this plugin as well as PouchDB using the npm with the following command or download from this store-client & PouchBD. Note that I'm using PouchDB 6.1.2 and hoodie-store-client 7.0.1.

npm install --save pouchdb

npm install --save @hoodie/store-client

Now, include these files to the page.

<script src="node_modules/pouchdb/dist/pouchdb.min.js"></script>
<script src="node_modules/@hoodie/store-client/dist/hoodie-store-client.js"></script>

With that finished, we'll add a new file called index.js in the asset folder and add a link to this file in the page. Inside this file, the first thing we need to do is initialize a Store object with the name of the database and URL to the server for synchronization.

$(function(){ 
  var store = new Store('example', { remote: 'http://localhost:4984/example', PouchDB: PouchDB });
});

Add the following code to save the value entered in the form and also add it to the list of contact on the page:

$('#contactForm').submit(function(event) {
    event.preventDefault();

    var name = $('#name').val();
    var email = $('#email').val();
    var mobile = $('#mobile').val();

    // Save the contact to the database
    store.add({
        name: name,
        mobile: mobile,
        email: email
    });

    $('#contactForm')[0].reset();
    });

    //add new contact to the page
    function addNewContactToList(contact) {
    var newContact = '<tr><td>' + contact.name + '</td><td>' + contact.mobile + '</td><td>' + contact.email + '</td></tr>'
    $("#contactList tbody").append(newContact);
    }

    //when a new entry is added to the database, run the corresponding function
    store.on('add', addNewContactToList);

    function loadContacts() {
    store.findAll().then(function(contacts) {
      var tbody = '';
      $.each(contacts, function (i, contact) {
        var row = '<tr><td>' + contact.name + '</td><td>' + contact.mobile + '</td><td>' + contact.email + '</td></tr>';
        tbody += row;
      });

      $("#contactList tbody").html('').html(tbody);
    });
  }

  // when the site loads in the browser,
  // we load all previously saved contacts from hoodie
  loadContacts();

I used store.add to insert to the local database, and then listened to events using store.on, particularly the add eventinsert — this will be useful when we start synchronizing data and will display new data after a complete synchronization with remote databases. I also added function to display local data when the page is loaded or refreshed.

local data

We now have offline-first data access/storage working. This will store data locally first, and push changes to the server afterwards when it's online. Our next step is to sync this data with a Couchbase server. To make this work, we need Couchbase Sync Gateway.

Sync Gateway

Sync Gateway is a secure web gateway application with synchronization, REST, stream, batch and event APIs for accessing and synchronizing data over the web. Sync Gateway enables, among other things, secure data replication between Couchbase Server and
Couchbase Lite and/or PouchDB.

Installing Sync Gateway

To install Sync Gateway,

  1. Download the file related to the operating system you're running here.

  2. Install

    • Windows: run the executable file you downloaded.
    • Mac: unpack the gzipped file tar xzf couchbase-sync-gateway-enterprise_1.3.1.1-1_x86_64.tar.gz.
  3. Start the Sync Gateway service (optional).

    • Windows: Use the Control Panel > Admin Tools > Services to stop/start the service.
    • Mac: Change directory to the directory you unzipped the downloaded file.
    • Mac: Then run this command in the shell bin/sync_gateway examples/basic-couchbase-bucket.json. The second argument is the configuration file for the Sync Gateway.

If Sync Gateway is installed, it'll be accessible from the http://localhost:4984 when started, and the administrative interface by navigating in the browser to http://localhost:4985/_admin/. If you remember, we had a similar url when initializing the Store object to use PouchDB but with an extra /example appended to it. The extra appendix specifies the name of the database we sync with. To get our app using offline data storage and synchronization, let's start the Sync Gateway service and update the page's JavaScript for automatic sync. I'll use this configuration setting for Sync Gateway.

{
    "log":["*"],
    "databases": {
        "example": {
            "server":"walrus:",
            "users": {
                "GUEST": {
                    "disabled": false,
                    "admin_channels": ["*"]
                }
            }
        }
    },
    "CORS": {
        "Origin": ["http://127.0.0.1:8801"],
        "LoginOrigin": ["http://127.0.0.1:8801"],
        "Headers": ["Content-Type"],
        "MaxAge": 17280000
    }
}

We have a basic configuration which will establish a connection to the example database and use the in-memory storage option (walrus), which in production we should change to point to a Couchbase server. We also added setting to allow cross origin resource sharing for our app which is on a different port (localhost:8801). We now have to start the Sync Gateway service running the following command in the terminal

$ ./bin/sync_gateway my-config/phonebook-config.json

start sync gateway

With Sync Gateway started we have to update the code for continuous synchronization. Add the following code to index.js

store.connect();

The code above tells PouchDB to start a continuous replication with the remote database. Once we reload our database, the data we added in the previous step will automatically be synced to the server. You can see this using the Sync Gateway admin URL http://localhost:4985/_admin/db/example.

Wrap Up

With all we have now, the app works well with offline data storage and real-time cross-device synchronization. You can run and test that it works when online and offline, and data is automatically synchronised and data conflicts resolved automatically. We have used walrus, the in-memory store on Sync Gateway, but for production, we will have to switch it to the url of our couchbase server.

Below are some GIFs that show how it works online and offline. You can go offline by turning the offline mode on chrome!

gif-1.gif

gif-2.gif

Feel free to grab the sourcecode here and give it a spin yourself!

Discover and read more posts from Peter Mbanugo
get started
Enjoy this post?

Leave a like and comment for Peter

6