I am playing with a cool Node / browser NoSQL database called PouchDB. It works as a uniform interface across all modern desktop and mobile browsers by utilizing IndexedDB / WebSQL technologies, and works under Node too. It can also proxy requests to a CouchDB database, the database behind NPM registry.
Simple example
Here is how simple it is to get started with PouchDB under Node.
npm install pouchdb --save
A simple source to insert an item and then print all items
1 | var PouchDB = require('pouchdb'); |
The output (we are using timestamps as ids)
Successfully saved a todo! 2015-03-23T19:44:45.106Z
fetched 1 items
[ { id: '2015-03-23T19:44:45.106Z',
key: '2015-03-23T19:44:45.106Z',
value: { rev: '1-cf1d4cfef10eca49836f072566e29fbe' },
doc:
{ title: 'an example todo',
completed: false,
_id: '2015-03-23T19:44:45.106Z',
_rev: '1-cf1d4cfef10eca49836f072566e29fbe' } } ]
Notice that this created a folder locally called my_database
with all data stored (with revisions) in binary format.
If we run this problem several times we will insert a new item into the store every time. Thus on the second run you should inspect seeing a list of 2 items, then 3 items, etc.
Destroying a db in the browser
If you want to remove an existing pouch database stored on the client, execute the destroy call
1 | (new PouchDB('db-name')).destroy().then(console.log.bind(console), console.error.bind(console)); |
Simple example using memory only
For unit testing we might want to avoid writing anything to the disk. The PouchDB uses adapters
to perform low-level
database operations. There is an adapter for keeping data in memory, without leaving any traces on the disk. The
memory adapter is perfect for unit testing. First install memdown,
then perform same operations npm install memdown --save
1 | var PouchDB = require('pouchdb'); |
Running this example always starts from empty (new) database, guaranteeing the unit tests will execute in a clean environment.
Promise-based API
The included PouchDB api is node-like callback based. Every function accepts a callback with the error (if any) at the first position and data at the second. I prefer using promises to callbacks, thus I convert each method to a promise-returning one using Q.nbind method
1 | var Q = require('q'); |
If we need to insert several todos, I prefer to link them to existing promise chain and using partial application instead of extra functions
1 | Q() |
Promisfying all methods
Q api has a function to promisify a single method, while another library of promises bluebird
has a method to promisify all object's methods at once. For each method foo.bar(..., callback(err, ...))
it will create a promise-returning foo.barAsync()
method.
1 | var Promise = require('bluebird'); |
Removing a little bit of boilerplate
We can avoid all partial application calls like addTodo.bind(null, 'first todo')
using
a curried function, like I described in Remove boilerplate from promise chains.
1 | // same DB code |
The curried version of addTodo
is a pleasure to work with in the middle of the promise chain.
Removing a lot of boilerplate using generators
Promises are nice, but we can do better using generators. Instead of creating and chaining promises, I am going to yield individual asynchronous functions into a runner function co. First, I am converting a function that takes a callback into a thunk using thunkify. A thunk just shifts the last callback argument into new function
1 | // original db.put |
We just need to return thunks from db.put
and db.allDocs
methods.
1 | var PouchDB = require('pouchdb'); |
We need to run this code using node --harmony index.js
flag (or use io.js).
It prints two JSON documents
[ { title: 'second todo',
completed: false,
_id: '2015-03-24T03:52:11.439Z',
_rev: '1-92c5d74cc0e408c35d332572c5fa1337' },
{ title: 'first todo',
completed: false,
_id: '2015-03-24T03:52:11.428Z',
_rev: '1-f90e06ba4ee3b752fe476e397d8ef993' } ]
We are going to insert two items, then grab results - and the sequence is written linearly, while
the execution is asynchronous. The yielded and continued values are controlled by the library function co
1 | co(function *() { |
The co
function returns a promise that is resolved with the last returned value.
Cleaner syntax by yielding promises
We can apply the same good code refactoring principles to the generators as to our usual code. Plus instead of creating the thunks we can directly yield promises. To run the generator function we can you another method from Bluebird API Promise.coroutine
1 | var PouchDB = require('pouchdb'); |
I like this approach for only using a single external promise library "bluebird".
Removing boilerplate using ES7 await / async
We can go one step further and use a new pair of keywords that will probably be a part of the EcmaScript7 standard.
While await/async
feature is in flux, one can already use it for experiments. Instead of a yield
I am using
the await
keyword, and instead of the generator function, I have marked a function with the keyword async
.
Each individual DB operation is returning a promise created via promisifyAll
method.
1 | var PouchDB = require('pouchdb'); |
1 | // most important function |
To run this example under Node 0.12 I created a second file and installed es6-module-loader as a dependency
1 | var System = require('es6-module-loader').System; |
Then I ran it using regular Node command node async-await-app.js
.
The ES7 code from async-await.js
was automatically transpiled using traceur on module load.