IBM Developer Advocacy

Offline-first QR-code Badge Scanner

Glynn Bird

Offline-first web applications are websites with a twist; they instruct the browser to cache all of the assets they need to render themselves, such as images, css, and JavaScript files. Once loaded, the websites continue to function even when there is a flaky or non-existent network connection. The killer feature of such apps is that they can use in-browser storage to read and write dynamic data without relying on the presence of a cloud server. PouchDB lets the web application store data in the browser using a variety of local storage mechanisms, while presenting a simple API. Furthermore, when it does find a network connection, a PouchDB database can sync with a remote Apache® CouchDB™ or Cloudant database, and changes flow seamlessly in both directions without loss of data.

Last year I made a simple offline-first data collection app that lets you design an HTML form and then use it to capture structured data, which is stored in PouchDB. My developer advocate colleagues used the app to collect submissions for a competition at a tech conference where the wifi was so poor that offline-first was the only option. When they returned home, they synced the PouchDB in their iPads to a shared Cloudant database.

I thought I’d revisit this app to allow it to scan conference badges that contain a QR code. In fact, I ended up writing a whole new app.

QR codes

QR Codes are two-dimensional barcodes containing a text payload that is encoded in black squares on a white background.

The payload can be a URL or some text. At conferences, the payload tends to be a vCard—a blob of text that contains the attendee’s name, email, company, and url. The more data the QR code has to store, the more detailed the blocks on the QR code image have to be:

url vCard
url vCard

The URL example above contains only The vCard example contains:

FN:Glynn Bird
TITLE:Developer Advocate
ADR;work:;;1 The Square;Bristol;;BS1 6DG;UK

Leveraging open-source

At first, I thought I would have to create a native iPhone or Android app to capture images from a camera, decode QR codes, and store data in a database. Fortunately other open-source heroes have solved the hard problems for me:

Still, it’s not quite that simple. The MediaStream API is new and not universally supported, so my code has to fall back on the older but also not universally supported getUserMedia API. I was also unable to get the media streaming code to work properly on mobile devices. Conversely, the AppCache API is deprecated but its replacement, Service Workers, is not widely supported. Developers have to make such compromises every day; weighing established but deprecated functions against the latest bleeding edge code that doesn’t have wide browser support.

The finished demo app uses all of the above technologies in a single-page web app that can be deployed to IBM Bluemix. Once you visit the page, it should be cached by your browser – try turning off your wifi and revisiting the page:

badgescanner gif

How does it work?

The web page contains a video tag, which the JavaScript uses to render a real-time feed of your machine’s webcam. The first time you open the website, you should be asked for permission for the app to access your webcam’s feed. There is also an invisible “canvas” control in the HTML markup, which takes a snapshot of the image every 0.5s. The data in the canvas goes to the QR-code parsing library, which returns some data if it finds a QR code on the canvas image.

The QR-code is parsed and turned into a JSON object:

    "version": "3.0",
    "fn": "Glynn Bird",
    "org": "IBM",
    "title": "Developer Advocate",
    "adr": ",,1 The Square,Bristol,,BS1 6DG,UK",
    "tel": "01179295012",
    "email": "",
    "url": "",
    "ts": 1461074275541,
    "date": "2016-04-19T13:57:55.541Z"

which is saved to a PouchDB database using the function call.

Below the real-time video feed, is a table of previously saved cards presented in “newest-first” order. This is achieved by querying the PouchDB database using a Map/Reduce index ordered on the ts (timestamp) value we created inside the JSON object.

Syncing to Cloudant

In PouchDB, syncing to a remote CouchDB or Cloudant database is a simple as calling the replicateTo function:
    .on("change", function(info) {
      // something changed
    .on("complete", function(info) {
      // all done
    .on("error",  function(err) {
      // something went wrong

The variable remoteDB contains a URL of the remote database in the form:


Creating offline-first applications is in some ways easier than creating traditional client-server applications. Your database is always available because it resides on the same device as the browser, making for fast performance and 100% uptime. The hard part—getting data from the client to the server and vice versa—is handled for you by PouchDB/CouchDB/Cloudant replication, which requires only a single function call to initiate the process. Allowing webpages to render and function without a network lets web apps go places they couldn’t normally go to:

  • capturing health data in developing countries
  • recording IoT data from remote sites
  • collecting information when the network is down or unusably slow

Combining PouchDB with Cloudant makes it easy to create such applications, without getting into native application development.


© “Apache”, “CouchDB,” and “Apache CouchDB” are trademarks or registered trademarks of The Apache Software Foundation. All other brands and trademarks are the property of their respective owners.

blog comments powered by Disqus