local-first apps: CouchDB❤️PouchDB
I guess every developer, when building an application, had the experience of being online all the time. Or what if the server is having a bad day and you need to access your data nonetheless? This is where local-first apps step in.
Welcome to the first part of the local-first apps series. In this series, we will look into the beautiful idea of giving the data back to the user, making the app work offline by default. We will start with looking at my favorite NoSQL database: CouchDB.
In future parts of the series, we will at least look at ServiceWorkers.
CouchDB: Syncing prowess
Let us be honest, Apache CouchDB is not the hot new thing. It is not the shiny toy that all the cool kids are playing with. It has been around for some time, and maybe your boss thinks it is a little stuffy. But what it is, is a reliable, easy to deploy sync engine.
CouchDB is a Document Database which operates on JSON documents. What makes it special, and what we love about it, is its built-in replication feature. It can sync databases with other instances, both bi-directionally and uni-directionally. It also speaks REST by default, which is also really nice. If you want REST service implemented right, take a look at the CouchDB API.
It also offers pretty good methods for authentication and authorization, including JWT and cookies.
In combination with its little brother, PouchDB, it enables syncing data from the browser to a central instance and vice versa, always updating and being available.
CouchDB and PouchDB
PouchDB is an in-browser database that speaks the CouchDB protocol. It is, to put it simply, a little CouchDB that lives right in the user's browser, using IndexedDB or WebSQL.
The concept is very simple: PouchDB stores the data locally, and when a connection is available, it seamlessly syncs with the remote CouchDB server. The combination is a true love story: CouchDB provides the robust backend and PouchDB provides the local offline availability. This makes your app incredibly fast and resilient, since the data is right where it needs to be: local to the user. This way, you can create a beautiful, custom experience that signals to the user that they are in full control of their data, even without a network.
A Simple Syncing Code Block
The actual code to set up this syncing is surprisingly easy, which is a good thing! You just declare your local and remote databases and then start the replication.
// First, we create our local PouchDB instance
const localDB = new PouchDB("my-awesome-local-db");
// Then, we point to our remote CouchDB instance
const remoteDB = new PouchDB("https://couchdb.my-server.com/awesome-remote-db");
// Now, the magic! Bi-directional, continuous sync
const sync = PouchDB.sync(localDB, remoteDB, {
live: true, // Keep syncing as changes happen
retry: true, // If it fails, try again later. Very good!
})
.on("change", function (info) {
// Something has changed, now we update the UI
console.log("Something synced!", info);
})
.on("error", function (err) {
// Uh oh, big error!
console.error("Replication Error:", err);
});
As you can see, the .sync() function takes care of both pushing local changes to the server and pulling remote changes to the client. All the hard work of conflict resolution and ensuring eventual consistency is handled for you. It's truly a thing of beauty.
And from then on, you can just add the documents locally. If you are online, they get synced at that moment. If you are offline, they get synced the next time you have a connection.
// Adding a new document is as simple as this
const newDocument = {
type: "todo",
title: "Learn about local-first apps",
completed: false,
createdAt: new Date().toISOString(),
};
// Add it to our local database
localDB
.put(newDocument)
.then(function (response) {
console.log("Document added successfully!", response);
// The document is now stored locally and will sync when online
})
.catch(function (err) {
console.error("Error adding document:", err);
});
Conclusion
The CouchDB/PouchDB combo is an elegant solution for anyone who wants to build a truly resilient, local-first application. We are not dealing with a fragile, online-only system anymore. We are dealing with a database that puts the user first. In theory it also scales well, but to be honest I never really had to do that. I mainly developed apps with it that really fit to the NoSQL model. Mainly my note taking and personal fitness app.