Nano: The official Apache CouchDB library for Node.js

Clone this repo:
  1. f708ba0 added .asf.yaml for repository config (#237) by Glynn Bird · 3 weeks ago main
  2. b3f33b6 9.0.1 by Glynn Bird · 3 weeks ago v9.0.1
  3. 6131f56 ensure only one HttpAgent is created (not one per request) - fixes issue #234 (#235) by Glynn Bird · 3 weeks ago
  4. 8e52b3d remove old migration guide by Glynn Bird · 4 weeks ago 9.0.0
  5. 7e0d61d 9.0.0 README by Glynn Bird · 4 weeks ago

Build StatusCoveragedependencies StatusNPM

Nano

Offical Apache CouchDB library for Node.js.

Features:

  • Minimalistic - There is only a minimum of abstraction between you and CouchDB.
  • Pipes - Proxy requests from CouchDB directly to your end user. ( ...AsStream functions only)
  • Promises - The vast majority of library calls return native Promises.
  • TypeScript - Detailed TypeScript definitions are built in.
  • Errors - Errors are proxied directly from CouchDB: if you know CouchDB you already know nano.

Installation

  1. Install npm
  2. npm install nano

or save nano as a dependency of your project with

npm install --save nano

Note the minimum required version of Node.js is 10.

Table of contents

Getting started

To use nano you need to connect it to your CouchDB install, to do that:

const nano = require('nano')('http://localhost:5984');

The URL you supply may also contain authentication credentials e.g. http://admin:mypassword@localhost:5984.

To create a new database:

nano.db.create('alice');

and to use an existing database:

const alice = nano.db.use('alice');

Under-the-hood, calls like nano.db.create are making HTTP API calls to the CouchDB service. Such operations are asynchronous. There are two ways to recieve the asynchronous data back from the library

  1. Promises
nano.db.create('alice').then((data) => {
  // success - response is in 'data'
}).catch((err) => {
  // failure - error information is in 'err'
})
  1. Callbacks
nano.db.create('alice', (err, data) => {
  // errors are in 'err' & response is in 'data'
})

In nano the callback function receives always three arguments:

  • err - The error, if any.
  • body - The HTTP response body from CouchDB, if no error. JSON parsed body, binary for non JSON responses.
  • header - The HTTP response header from CouchDB, if no error.

The documentation will now follow the Promises style.


A simple but complete example in the async/await style:

async function asyncCall() {
  await nano.db.destroy('alice')
  await nano.db.create('alice')
  const alice = nano.use('alice')
  const response = await alice.insert({ happy: true }, 'rabbit')
  return response
}
asyncCall()

or in the raw Promises-style

const nano = require('nano')('http://localhost:5984');
let alice;

nano.db.destroy('alice').then((response) => {
  return nano.db.create('alice')
}).then((response) =>  {
  alice = nano.use('alice')
  return alice.insert({ happy: true }, 'rabbit')
}).then((response) => {
  console.log('you have inserted a document with an _id of rabbit')
  console.log(response);
})

If you run either of these examples (after starting CouchDB) you will see:

you have inserted a document with an _id of rabbit.
{ ok: true,
  id: 'rabbit',
  rev: '1-6e4cb465d49c0368ac3946506d26335d' }

You can also see your document in futon.

Configuration

Configuring nano to use your database server is as simple as:

const nano = require('nano')('http://localhost:5984')
const db = nano.use('foo');

If you don't need to instrument database objects you can simply:

// nano parses the URL and knows this is a database
const db = require('nano')('http://localhost:5984/foo');

You can also pass options to the require to specify further configuration options you can pass an object literal instead:

// nano parses the URL and knows this is a database
const opts = {
  url: "http://localhost:5984/foo",
  requestDefaults: { "proxy" : "http://someproxy" }
};
const db = require('nano')(opts);

Please check request for more information on the defaults. They support features like cookie jar, proxies, ssl, etc.

You can tell nano to not parse the URL (maybe the server is behind a proxy, is accessed through a rewrite rule or other):

// nano does not parse the URL and return the server api
// "http://localhost:5984/prefix" is the CouchDB server root
const couch = require('nano')(
  { url : "http://localhost:5984/prefix"
    parseUrl : false
  });
const db = couch.use('foo');

Pool size and open sockets

A very important configuration parameter if you have a high traffic website and are using nano is setting up the pool.size. By default, the Node.js HTTP global agent (client) has a certain size of active connections that can run simultaneously, while others are kept in a queue. Pooling can be disabled by setting the agent property in requestDefaults to false, or adjust the global pool size using:

http.globalAgent.maxSockets = 20;

You can also increase the size in your calling context using requestDefaults if this is problematic. Refer to the request documentation and examples for further clarification.

Here's an example explicitly using the keep alive agent (installed using npm install agentkeepalive), especially useful to limit your open sockets when doing high-volume access to CouchDB on localhost:

const agentkeepalive = require('agentkeepalive');
const myagent = new agentkeepalive({
  maxSockets: 50,
  maxKeepAliveRequests: 0,
  maxKeepAliveTime: 30000
});

const db = require('nano')(
  { url: "http://localhost:5984/foo",
    requestDefaults : { "agent" : myagent }
  });

TypeScript

There is a full TypeScript definition included in the the nano package. Your TypeScript editor will show you hints as you write your code with the nano library with your own custom classes:

import * as Nano  from 'nano'

let n = Nano('http://USERNAME:PASSWORD@localhost:5984')
let db = n.db.use('people')

interface iPerson extends Nano.MaybeDocument {
  name: string,
  dob: string
}

class Person implements iPerson {
  _id: string
  _rev: string
  name: string
  dob: string

  constructor(name: string, dob: string) {
    this._id = undefined
    this._rev = undefined
    this.name = name
    this.dob = dob
  }

  processAPIResponse(response: Nano.DocumentInsertResponse) {
    if (response.ok === true) {
      this._id = response.id
      this._rev = response.rev
    }
  }
}

let p = new Person('Bob', '2015-02-04')
db.insert(p).then((response) => {
  p.processAPIResponse(response)
  console.log(p)
})

Database functions

nano.db.create(name, [callback])

Creates a CouchDB database with the given name:

nano.db.create('alice').then((body) => {
  console.log('database alice created!');
})

nano.db.get(name, [callback])

Get information about the database name:

nano.db.get('alice').then((body) => {
  console.log(body);
})

nano.db.destroy(name, [callback])

Destroys the database name:

nano.db.destroy('alice').then((body) => {
  // database destroyed
})

nano.db.list([callback])

Lists all the CouchDB databases:

nano.db.list().then((body) => {
  // body is an array
  body.forEach((db) => {
    console.log(db);
  });
});

nano.db.listAsStream()

Lists all the CouchDB databases as a stream:

nano.db.list().pipe(process.stdout);

nano.db.compact(name, [designname], [callback])

Compacts name, if designname is specified also compacts its views.

nano.db.replicate(source, target, [opts], [callback])

Replicates source to target with options opts. The targetdatabase has to exist, add create_target:true to opts to create it prior to replication:

nano.db.replicate('alice', 'http://admin:password@otherhost.com:5984/alice',
                  { create_target:true }).then((body) => {
  console.log(body);
});

nano.db.replication.enable(source, target, [opts], [callback])

Enables replication using the new CouchDB api from source to target with options opts. target has to exist, add create_target:true to opts to create it prior to replication. Replication will survive server restarts.

nano.db.replication.enable('alice', 'http://admin:password@otherhost.com:5984/alice',
                  { create_target:true }).then((body) => {
  console.log(body);
});

nano.db.replication.query(id, [opts], [callback])

Queries the state of replication using the new CouchDB API. The id comes from the response given by the call to replication.enable:

nano.db.replication.enable('alice', 'http://admin:password@otherhost.com:5984/alice',
                   { create_target:true }).then((body) => {
  return nano.db.replication.query(body.id);
}).then((response) => {
  console.log(response);
});

nano.db.replication.disable(id, [opts], [callback])

Disables replication using the new CouchDB API. The id comes from the response given by the call to replication.enable:

nano.db.replication.enable('alice', 'http://admin:password@otherhost.com:5984/alice',
                   { create_target:true }).then((body) => {
  return nano.db.replication.disable(body.id);
}).then((response) => {
  console.log(response);
});

nano.db.changes(name, [params], [callback])

Asks for the changes feed of name, params contains additions to the query string.

nano.db.changes('alice').then((body) => {
  console.log(body);
});

nano.db.changesAsStream(name, [params])

Same as nano.db.changes but returns a stream.

nano.db.changes('alice').pipe(process.stdout);

nano.db.info([callback])

Gets database information:

nano.db.info().then((body) => {
  console.log('got database info', body);
});

nano.use(name)

Returns a database object that allows you to perform operations against that database:

const alice = nano.use('alice');
alice.insert({ happy: true }, 'rabbit').then((body) => {
  // do something
});

nano.db.use(name)

Alias for nano.use

nano.db.scope(name)

Alias for nano.use

nano.scope(name)

Alias for nano.use

nano.request(opts, [callback])

Makes a custom request to CouchDB. This can be used to create your own HTTP request to the CouchDB server, to perform operations where there is no nano function that encapsulates it. The available opts are:

  • opts.db – the database name
  • opts.method – the http method, defaults to get
  • opts.path – the full path of the request, overrides opts.doc and opts.att
  • opts.doc – the document name
  • opts.att – the attachment name
  • opts.qs – query string parameters, appended after any existing opts.path, opts.doc, or opts.att
  • opts.content_type – the content type of the request, default to json
  • opts.headers – additional http headers, overrides existing ones
  • opts.body – the document or attachment body
  • opts.encoding – the encoding for attachments
  • opts.multipart – array of objects for multipart request
  • opts.stream - if true, a request object is returned. Default false and a Promise is returned.

nano.relax(opts, [callback])

Alias for nano.request

nano.config

An object containing the nano configurations, possible keys are:

  • url - the CouchDB URL
  • db - the database name

nano.updates([params], [callback])

Listen to db updates, the available params are:

  • params.feed – Type of feed. Can be one of
  • longpoll: Closes the connection after the first event.
  • continuous: Send a line of JSON per event. Keeps the socket open until timeout.
  • eventsource: Like, continuous, but sends the events in EventSource format.
  • params.timeout – Number of seconds until CouchDB closes the connection. Default is 60.
  • params.heartbeat – Whether CouchDB will send a newline character (\n) on timeout. Default is true.

Document functions

db.insert(doc, [params], [callback])

Inserts doc in the database with optional params. If params is a string, it‘s assumed it is the intended document _id. If params is an object, it’s passed as query string parameters and docName is checked for defining the document _id:

const alice = nano.use('alice');
alice.insert({ happy: true }, 'rabbit').then((body) => {
  console.log(body);
});

The insert function can also be used with the method signature db.insert(doc,[callback]), where the doc contains the _id field e.g.

const alice = nano.use('alice')
alice.insert({ _id: 'myid', happy: true }).then((body) => {
  console.log(body)
})

and also used to update an existing document, by including the _rev token in the document being saved:

const alice = nano.use('alice')
alice.insert({ _id: 'myid', _rev: '1-23202479633c2b380f79507a776743d5', happy: false }).then((body) => {
  console.log(body)
})

db.destroy(docname, rev, [callback])

Removes a document from CouchDB whose _id is docname and who's revision is _rev:

alice.destroy('rabbit', '3-66c01cdf99e84c83a9b3fe65b88db8c0').then((body) => {
  console.log(body);
});

db.get(docname, [params], [callback])

Gets a document from CouchDB whose _id is docname:

alice.get('rabbit').then((body) => {
  console.log(body);
});

or with optional query string params:

alice.get('rabbit', { revs_info: true }).then((body) => {
  console.log(body);
});

db.head(docname, [callback])

Same as get but lightweight version that returns headers only:

alice.head('rabbit').then((headers) => {
  console.log(headers);
});

Note: if you call alice.head in the callback style, the headers are returned to you as the third argument of the callback function.

db.bulk(docs, [params], [callback])

Bulk operations(update/delete/insert) on the database, refer to the CouchDB doc e.g:

const documents = [
  { a:1, b:2 },
  { _id: 'tiger', striped: true}
];
alice.bulk({docs:documents}).then((body) => {
  console.log(body);
});

db.list([params], [callback])

List all the docs in the database .

alice.list().then((body) => {
  body.rows.forEach((doc) => {
    console.log(doc);
  });
});

or with optional query string additions params:

alice.list({include_docs: true}).then((body) => {
  body.rows.forEach((doc) => {
    // output each document's body
    console.log(doc.doc);
  });
});

db.listAsStream([params])

List all the docs in the database as a stream.

alice.list().pipe(process.stdout)

db.fetch(docnames, [params], [callback])

Bulk fetch of the database documents, docnames are specified as per CouchDB doc. additional query string params can be specified, include_docs is always set to true.

const keys = ['tiger', 'zebra', 'donkey'];
alice.fetch({keys: keys}).then((data) => {
  console.log(data);
});

db.fetchRevs(docnames, [params], [callback])

** changed in version 6 **

Bulk fetch of the revisions of the database documents, docnames are specified as per CouchDB doc. additional query string params can be specified, this is the same method as fetch but include_docs is not automatically set to true.

db.createIndex(indexDef, [callback])

Create index on database fields, as specified in CouchDB doc.

const indexDef = {
  index: { fields: ['foo'] },
  name: 'fooindex'
};
alice.createIndex(indexDef).then((result) => {
  console.log(result);
});

Reading Changes Feed

Nano provides a low-level API for making calls to CouchDB's changes feed, or if you want a reliable, resumable changes feed follower, then you need the changesReader.

There are three ways to start listening to the changes feed:

  1. changesReader.start() - to listen to changes indefinitely by repeated “long poll” requests. This mode continues to poll for changes forever.
  2. changesReader.get() - to listen to changes until the end of the changes feed is reached, by repeated “long poll” requests. Once a response with zero changes is received, the ‘end’ event will indicate the end of the changes and polling will stop.
  3. changesReader.spool() - listen to changes in one long HTTP request. (as opposed to repeated round trips) - spool is faster but less reliable.

Note: for .get() & .start(), the sequence of API calls can be paused by calling changesReader.pause() and resumed by calling changesReader.resume().

Set up your database connection and then choose changesReader.start() to listen to that database's changes:

const db = nano.db.use('mydb')
db.changesReader.start()
  .on('change', (change) => { console.log(change) })
  .on('batch', (b) => {
    console.log('a batch of', b.length, 'changes has arrived');
  }).on('seq', (s) => {
    console.log('sequence token', s);
  }).on('error', (e) => {
    console.error('error', e);
  })

Note: you probably want to monitor either the change or batch event, not both.

If you want changesReader to hold off making the next _changes API call until you are ready, then supply wait:true in the options to get/start. The next request will only fire when you call changesReader.resume():

db.changesReader.get({wait: true})
  .on('batch', (b) => {
    console.log('a batch of', b.length, 'changes has arrived');
    // do some asynchronous work here and call "changesReader.resume()" 
    // when you're ready for the next API call to be dispatched.
    // In this case, wait 5s before the next changes feed request.
    setTimeout( () => {
      db.changesReader.resume()
    }, 5000)
  }).on('end', () => {
    console.log('changes feed monitoring has stopped');
  });

You may supply a number of options when you start to listen to the changes feed:

ParameterDescriptionDefault valuee.g.
batchSizeThe maximum number of changes to ask CouchDB for per HTTP request. This is the maximum number of changes you will receive in a batch event.100500
sinceThe position in the changes feed to start from where 0 means the beginning of time, now means the current position or a string token indicates a fixed position in the changes feednow390768-g1AAAAGveJzLYWBgYMlgTmGQ
includeDocsWhether to include document bodies or notfalsee.g. true
waitFor get/start mode, automatically pause the changes reader after each request. When the the user calls resume(), the changes reader will resume.falsee.g. true
fastChangesAdds a seq_interval parameter to fetch changes more quicklyfalsetrue
selectorFilters the changes feed with the supplied Mango selector{“name”:"fred}null
timeoutThe number of milliseconds a changes feed request waits for data6000010000

The events it emits are as follows:s

EventDescriptionData
changeEach detected change is emitted individually. Only available in get/start modes.A change object
batchEach batch of changes is emitted in bulk in quantities up to batchSize.An array of change objects
seqEach new sequence token (per HTTP request). This token can be passed into ChangesReader as the since parameter to resume changes feed consumption from a known point. Only available in get/start modes.String
errorOn a fatal error, a descriptive object is returned and change consumption stops.Error object
endEmitted when the end of the changes feed is reached. ChangesReader.get() mode only,Nothing

The ChangesReader library will handle many temporal errors such as network connectivity, service capacity limits and malformed data but it will emit an error event and exit when fed incorrect authentication credentials or an invalid since token.

The change event delivers a change object that looks like this:

{
	"seq": "8-g1AAAAYIeJyt1M9NwzAUBnALKiFOdAO4gpRix3X",
	"id": "2451be085772a9e588c26fb668e1cc52",
	"changes": [{
		"rev": "4-061b768b6c0b6efe1bad425067986587"
	}],
	"doc": {
		"_id": "2451be085772a9e588c26fb668e1cc52",
		"_rev": "4-061b768b6c0b6efe1bad425067986587",
		"a": 3
	}
}

N.B

  • doc is only present if includeDocs:true is supplied
  • seq is not present for every change

The id is the unique identifier of the document that changed and the changes array contains the document revision tokens that were written to the database.

The batch event delivers an array of change objects.

Partition Functions

Functions related to partitioned databses.

Create a partitioned database by passing { partitioned: true } to db.create:

await nano.db.create('my-partitioned-db', { partitioned: true })

The database can be used as normal:

const db = nano.db.use('my-partitioned-db')

but documents must have a two-part _id made up of <partition key>:<document id>. They are insert with db.insert as normal:

const doc = { _id: 'canidae:dog', name: 'Dog', latin: 'Canis lupus familiaris' }
await db.insert(doc)

Documents can be retrieved by their _id using db.get:

const doc = db.get('canidae:dog')

Mango indexes can be created to operate on a per-partition index by supplying partitioned: true on creation:

const i = {
  ddoc: 'partitioned-query',
  index: { fields: ['name'] },
  name: 'name-index',
  partitioned: true,
  type: 'json'
}
 
// instruct CouchDB to create the index
await db.index(i)

Search indexes can be created by writing a design document with opts.partitioned = true:

// the search definition
const func = function(doc) {
  index('name', doc.name)
  index('latin', doc.latin)
}
 
// the design document containing the search definition function
const ddoc = {
  _id: '_design/search-ddoc',
  indexes: {
    search-index: {
      index: func.toString()
    }
  },
  options: {
    partitioned: true
  }
}
 
await db.insert(ddoc)

MapReduce views can be created by writing a design document with opts.partitioned = true:

const func = function(doc) {
  emit(doc.family, doc.weight)
}
 
// Design Document
const ddoc = {
  _id: '_design/view-ddoc',
  views: {
    family-weight: {
      map: func.toString(),
      reduce: '_sum'
    }
  },
  options: {
    partitioned: true
  }
}
 
// create design document
await db.insert(ddoc)

db.partitionInfo(partitionKey, [callback])

Fetch the stats of a single partition:

const stats = await alice.partitionInfo('canidae')

db.partitionedList(partitionKey, [params], [callback])

Fetch documents from a database partition:

// fetch document id/revs from a partition
const docs = await alice.partitionedList('canidae')

// add document bodies but limit size of response
const docs = await alice.partitionedList('canidae', { include_docs: true, limit: 5 })

db.partitionedListAsStream(partitionKey, [params])

Fetch documents from a partition as a stream:

// fetch document id/revs from a partition
nano.db.partitionedListAsStream('canidae').pipe(process.stdout)

// add document bodies but limit size of response
nano.db.partitionedListAsStream('canidae', { include_docs: true, limit: 5 }).pipe(process.stdout)

db.partitionedFind(partitionKey, query, [params])

Query documents from a partition by supplying a Mango selector:

// find document whose name is 'wolf' in the 'canidae' partition
await db.partitionedFind('canidae', { 'selector' : { 'name': 'Wolf' }})

db.partitionedFindAsStream(partitionKey, query)

Query documents from a partition by supplying a Mango selector as a stream:

// find document whose name is 'wolf' in the 'canidae' partition
db.partitionedFindAsStream('canidae', { 'selector' : { 'name': 'Wolf' }}).pipe(process.stdout)

db.partitionedSearch(partitionKey, designName, searchName, params, [callback])

Search documents from a partition by supplying a Lucene query:

const params = {
  q: 'name:\'Wolf\''
}
await db.partitionedSearch('canidae', 'search-ddoc', 'search-index', params)
// { total_rows: ... , bookmark: ..., rows: [ ...] }

db.partitionedSearchAsStream(partitionKey, designName, searchName, params)

Search documents from a partition by supplying a Lucene query as a stream:

const params = {
  q: 'name:\'Wolf\''
}
db.partitionedSearchAsStream('canidae', 'search-ddoc', 'search-index', params).pipe(process.stdout)
// { total_rows: ... , bookmark: ..., rows: [ ...] }

db.partitionedView(partitionKey, designName, viewName, params, [callback])

Fetch documents from a MapReduce view from a partition:

const params = {
  startkey: 'a',
  endkey: 'b',
  limit: 1
}
await db.partitionedView('canidae', 'view-ddoc', 'view-name', params)
// { rows: [ { key: ... , value: [Object] } ] }

db.partitionedViewAsStream(partitionKey, designName, viewName, params)

Fetch documents from a MapReduce view from a partition as a stream:

const params = {
  startkey: 'a',
  endkey: 'b',
  limit: 1
}
db.partitionedView('canidae', 'view-ddoc', 'view-name', params).pipe(process.stdout)
// { rows: [ { key: ... , value: [Object] } ] }

Multipart functions

db.multipart.insert(doc, attachments, params, [callback])

Inserts a doc together with attachments and params. If params is a string, it's assumed as the intended document _id. If params is an object, its passed as query string parameters and docName is checked for defining the _id. Refer to the doc for more details. The attachments parameter must be an array of objects with name, data and content_type properties.

const fs = require('fs');

fs.readFile('rabbit.png', (err, data) => {
  if (!err) {
    alice.multipart.insert({ foo: 'bar' }, [{name: 'rabbit.png', data: data, content_type: 'image/png'}], 'mydoc').then((body) => {
      console.log(body);
    });
  }
});

db.multipart.get(docname, [params], [callback])

Get docname together with its attachments via multipart/related request with optional query string additions params. Refer to the doc for more details. The multipart response body is a Buffer.

alice.multipart.get('rabbit').then((buffer) => {
  console.log(buffer.toString());
});

Attachments functions

db.attachment.insert(docname, attname, att, contenttype, [params], [callback])

Inserts an attachment attname to docname, in most cases params.rev is required. Refer to the CouchDB doc for more details.

const fs = require('fs');

fs.readFile('rabbit.png', (err, data) => {
  if (!err) {
    alice.attachment.insert('rabbit', 'rabbit.png', data, 'image/png',
      { rev: '12-150985a725ec88be471921a54ce91452' }).then((body) => {
        console.log(body);
    });
  }
});

db.attachment.insertAsStream(docname, attname, att, contenttype, [params])

As of Nano 9.x, the function db.attachment.insertAsStream is now deprecated. Now simply pass a readable stream to db.attachment.insert as the third paramseter.

db.attachment.get(docname, attname, [params], [callback])

Get docname's attachment attname with optional query string additions params.

const fs = require('fs');

alice.attachment.get('rabbit', 'rabbit.png').then((body) => {
  fs.writeFile('rabbit.png', body);
});

db.attachment.getAsStream(docname, attname, [params])

const fs = require('fs');

alice.attachment.getAsStream('rabbit', 'rabbit.png').pipe(fs.createWriteStream('rabbit.png'));

db.attachment.destroy(docname, attname, [params], [callback])

changed in version 6

Destroy attachment attname of docname's revision rev.

alice.attachment.destroy('rabbit', 'rabbit.png',
    {rev: '1-4701d73a08ce5c2f2983bf7c9ffd3320'}).then((body) => {
       console.log(body);
});

Views and design functions

db.view(designname, viewname, [params], [callback])

Calls a view of the specified designname with optional query string params. If you're looking to filter the view results by key(s) pass an array of keys, e.g { keys: ['key1', 'key2', 'key_n'] }, as params.

alice.view('characters', 'happy_ones', {
  'key': 'Tea Party',
  'include_docs': true
}).then((body) => {
  body.rows.forEach((doc) => {
    console.log(doc.value);
  });
});

or

alice.view('characters', 'soldiers', {
  'keys': ['Hearts', 'Clubs']
}).then((body) => {
  body.rows.forEach((doc) => {
    console.log(doc.value);
  });
});

When params is not supplied, or no keys are specified, it will simply return all documents in the view:

alice.view('characters', 'happy_ones').then((body) => {
  body.rows.forEach((doc) => {
    console.log(doc.value);
  });
});
alice.view('characters', 'happy_ones', { include_docs: true }).then((body) => {
  body.rows.forEach((doc) => {
    console.log(doc.value);
  });
});

db.viewAsStream(designname, viewname, [params])

Same as db.view but returns a stream:

alice.view('characters', 'happy_ones', {reduce: false}).pipe(process.stdout);

db.viewWithList(designname, viewname, listname, [params], [callback])

Calls a list function fed by the given view from the specified design document.

alice.viewWithList('characters', 'happy_ones', 'my_list').then((body) => {
  console.log(body);
});

db.viewWithListAsStream(designname, viewname, listname, [params], [callback])

Calls a list function fed by the given view from the specified design document as a stream.

alice.viewWithListAsStream('characters', 'happy_ones', 'my_list').then((body) => {
  console.log(body);
});

db.show(designname, showname, doc_id, [params], [callback])

Calls a show function from the specified design for the document specified by doc_id with optional query string additions params.

alice.show('characters', 'format_doc', '3621898430').then((doc) => {
  console.log(doc);
});

Take a look at the couchdb wiki for possible query paramaters and more information on show functions.

db.atomic(designname, updatename, docname, [body], [callback])

Calls the design's update function with the specified doc in input.

db.atomic("update", "inplace", "foobar",
{field: "foo", value: "bar"}).then((response) => {
  console.log(response);
});

Note that the data is sent in the body of the request. An example update handler follows:

"updates": {
  "in-place" : "function(doc, req) {
      var request_body = JSON.parse(req.body);

      var field = request_body.field;
      var value = request_body.value;
      var message = 'set ' + field + ' to ' + value;
      doc[field] = value;
      return [doc, message];
  }"
}

db.search(designname, searchname, params, [callback])

Calls a view of the specified design with optional query string additions params.

alice.search('characters', 'happy_ones', { q: 'cat' }).then((doc) => {
  console.log(doc);
});

or

const drilldown = [['author', 'Dickens']['publisher','Penguin']]
alice.search('inventory', 'books', { q: '*:*', drilldown: drilldown }).then((doc) => {
  console.log(doc);
});

Check out the tests for a fully functioning example.

db.searchAsStream(designname, searchname, params)

Calls a view of the specified design with optional query string additions params. Returns stream.

alice.search('characters', 'happy_ones', { q: 'cat' }).pipe(process.stdout);

db.find(selector, [callback])

Perform a “Mango” query by supplying a JavaScript object containing a selector:

// find documents where the name = "Brian" and age > 25.
const q = {
  selector: {
    name: { "$eq": "Brian"},
    age : { "$gt": 25 }
  },
  fields: [ "name", "age", "tags", "url" ],
  limit:50
};
alice.find(q).then((doc) => {
  console.log(doc);
});

db.findAsStream(selector)

Perform a “Mango” query by supplying a JavaScript object containing a selector, but return a stream:

// find documents where the name = "Brian" and age > 25.
const q = {
  selector: {
    name: { "$eq": "Brian"},
    age : { "$gt": 25 }
  },
  fields: [ "name", "age", "tags", "url" ],
  limit:50
};
alice.findAsStream(q).pipe(process.stdout);

using cookie authentication

Nano supports making requests using CouchDB's cookie authentication functionality. If you initialise Nano so that it is cookie-aware, you may call nano.auth first to get a session cookie. Nano will behave like a web browser, remembering your session cookie and refreshing it if a new one is received in a future HTTP response.

const nano = require('nano')({url: 'http://localhost:5984', requestDefaults: {jar:true}}),
  username = 'user',
  userpass = 'pass',
  db = nano.db.use('mydb');

nano.auth(username, userpass).then((() => {
  return db.get('mydoc');
}).then((doc) => {
  console.log(doc);
});

The second request works because the nano library has remembered the AuthSession cookie that was invisibily returned by the nano.auth call.

When you have a session, you can see what permissions you have by calling the nano.session function

nano.session().then((doc) => {
  console.log(doc)
  // { userCtx: { roles: [ '_admin', '_reader', '_writer' ], name: 'rita' },  ok: true }
});

Advanced features

Getting uuids

If your application needs to generate UUIDs, then CouchDB can provide some for you

nano.uuids(3).then((doc) => {
  console.log(doc);
});
// { uuids: [
// '5d1b3ef2bc7eea51f660c091e3dffa23',
// '5d1b3ef2bc7eea51f660c091e3e006ff',
// '5d1b3ef2bc7eea51f660c091e3e007f0',
//]}

The first parameter is the number of uuids to generate. If omitted, it defaults to 1.

Extending nano

nano is minimalistic but you can add your own features with nano.request(opts)

For example, to create a function to retrieve a specific revision of the rabbit document:

function getrabbitrev(rev) {
  return nano.request({ db: 'alice',
                 doc: 'rabbit',
                 method: 'get',
                 params: { rev: rev }
               });
}

getrabbitrev('4-2e6cdc4c7e26b745c2881a24e0eeece2').then((body) => {
  console.log(body);
});

Pipes

You can pipe the return values of certain nano functions like other stream. For example if our rabbit document has an attachment with name picture.png you can pipe it to a writable stream:

const fs = require('fs');
const nano = require('nano')('http://127.0.0.1:5984/');
const alice = nano.use('alice');
alice.attachment.getAsStream('rabbit', 'picture.png').pipe(fs.createWriteStream('/tmp/rabbit.png'));

then open /tmp/rabbit.png and you will see the rabbit picture.

Functions that return streams instead of a Promise are:

  • nano.db.listAsStream

attachment functions:

  • db.attachment.getAsStream
  • db.attachment.insertAsStream

and document level functions

  • db.listAsStream

Logging

When instantiating Nano, you may supply the function that will perform the logging of requests and responses. In its simplest for, simply pass console.log as your logger:

const nano = Nano({ url: process.env.COUCH_URL, log: console.log })
// all requests and responses will be sent to console.log

You may supply your own logging function to format the data before output:

const url = require('url')
const logger = (data) => {
  // only output logging if there is an environment variable set
  if (process.env.LOG === 'nano') {
    // if this is a request
    if (typeof data.err === 'undefined') {
      const u = new url.URL(data.uri)
      console.log(data.method, u.pathname, data.qs)
    } else {
      // this is a response
      const prefix = data.err ? 'ERR' : 'OK'
      console.log(prefix, data.headers.statusCode, JSON.stringify(data.body).length)
    }
  }
}
const nano = Nano({ url: process.env.COUCH_URL, log: logger })
// all requests and responses will be formatted by my code
// GET /cities/_all_docs { limit: 5 }
// OK 200 468

Tutorials, examples in the wild & screencasts

Roadmap

Check issues

Tests

To run (and configure) the test suite simply:

cd nano
npm install
npm run test

Meta

http://freenode.org/

Release

To create a new release of nano. Run the following commands on the main branch

  npm version {patch|minor|major}
  github push  origin main --tags
  npm publish