A simple CouchDB abstraction for NodeJS built on nano.
npm install couchdb-model
First, configure your database using nano.
var nano = require('nano')(COUCHDB_BASE_URL);
var dbHandle = nano.use(COUCHDB_DB_NAME);
Create your model
var couchDBModel = require('couchdb-model');
var myModel = couchDBModel(dbHandle);
Now, you can use myModel
to create a new document.
var document = myModel.create({
data: 'my_data',
createdAt: Date.now()
});
var documentWithID = myModel.create({
_id: 'my_unique_id',
data: 'my_data',
createdAt: Date.now()
});
You can persist the documents into the database. If no ID given, the instance
will be updated with the ID couchdb generated.
All functions and fields starting with _
will be discarded, except
_id
and _rev
.
document.save(function(error) {
if (error) console.error('failed to save document');
else console.log('document saved with id: ' + document._id);
});
To delete an document (ID will be reset to null)
document.delete(function(error) {
if (error) console.error('failed to delete document');
else console.log('document deleted.');
});
To find a document by ID:
myModel.findOneByID('my_unique_id', function(error, result) {
if (error) console.error('failed to get the document');
else console.log(result); // result is an model instance
});
To list all documents (requires admin rights, uses _all_docs
):
myModel.findAll(function(error, results) {
if (error) console.error('failed list documents');
else console.log(results); // result is an array of model instances
});
If a request fails, nano's error
parameter is just forwared to your callback.
See nano documentation for more information.
You can override the constructor wich is used by Model#create
and Model#find
methods.
In order to keep it working, you have to extend the original constructor.
var Model = couchDBModel(nano.use(COUCHDB_DB_NAME));
Model.instanceConstructor = function (model, data) {
couchDBModel.Instance.call(this, model, data);
// Instance constructor already applied all field in 'data' to 'this'.
this.passwordWithAnX = 'X' + this.password; // bulletproof encryption
this.password = undefined;
};
extend(Model.instanceConstructor.prototype, couchDBModel.Instance.prototype, {
checkPassword: function(password) {
return this.passwordWithAnX === 'X' + password;
}
});
var user = Model.create({ username: 'username', password: 'pw'});
user.save(function(error) {
Model.findOneByID(user._id, function(error, result) {
console.log(result.checkPassword('pw')); // outputs true
done();
});
});
All methods will be discarded when they are persisted to the database.
You can specify your views when you create your model, by passing a configuration object to the model factory function. The views
array in your options object can be just the path to the views, or an object, specifying the path and the name.
model = couchDBModel(db, {
views: [
'_design/article/_view/by_date',
{
path: '_design/article/_view/by_tag',
name: 'by_one_of_the_tags'
},
{
path: '_design/article/_view/by_slug'
}
]
});
The name
field is used to generate method names. If not given, the last segment of path
will be used. The name will be camelized to create nice method names.
The above example will create a model with the following methods:
model.findOneByDate
model.findManyByDate
model.findOneByOneOfTheTags
model.findManyByOneOfTheTags
model.findOneBySlug
model.findManyBySlug
findMany{ViewName}(startkey, [[[[endkey], sort], limit], skip], callback)
The above arguments are mapped to the corresponding CouchDB request parameters, except sort
, which can be "asc"
or "dsc"
.
findMany{ViewName}(null, params, callback)
If you provide null
as the first argument, the second argument will be treated as a CouchDB request parameter object, and will be passed to nano. You can find out about these parameters in the CouchDB docs.
There's no need to JSON.stringify
and URL encode the parameters, this is taken care of behind the scenes by the wonderful nano.
callback
is a standard node-style callback, and the second argument will be an array of instances.
findOne{viewname}(startkey, [[[endkey], sort], skip], callback)
findOne{viewname}(null, params, callback)
They work the same way as findMany
except that limit is always set to 1
, and the second argument to callback
will be an instance, not an array.
All async methods return a promise (created with Q), when there's no callback passed as the last argument.
If enabled, the model will generate an onRequest
method, which is a standard NodeJS request handler. The REST API can be enabled via the restapi
field in the configuration object.
If indexing is enabled, GET-ting the root path will return all documents in the database (if findAll has all the required permissions).
var myModel = couchDBModel(db, {
restapi: {
index: true
}
});
// GET / returns an array of all documents
Elements can be queried by ID by supplying an ID in the path, if it's enabled by the byID
flag. The ID cannot contain a slash.
var myModel = couchDBModel(db, {
restapi: {
byID: true
}
});
// GET /asdasd3wer will return the document with the id asdasd3wer, or 404 if not found
Views can be enabled one-by one, by setting flags in the views
object in the configuration. The view names should match the names in the config.views
object, in a camelcased form. In the request URLs the original view names can be used.
var myModel = couchDBModel(db, {
views: [
'_design/article/_view/by_date',
{
path: '_design/article/_view/by_tag',
name: 'by_one_of_the_tags'
},
{
path: '_design/article/_view/by_slug'
}
],
restapi: {
views: {
byOneOfTheTags: true,
bySlug: false
}
}
});
// GET /by_slug/something responds 403
There are two URL patterns for view requests: findOne, and with params.
View URLs can be accessed the same way as CouchDB views, with a query string. The standard CouchDB parameters can be used (startkey
, endkey
, etc.). This pattern is mapped to a Model#findManyBy{view}(null, {object} params, [{function} callback)]
call.
The result will be an array containing the result documents.
Example:
GET /by_date/?startkey=2014-01-01&endkey=2014-12-31
There's a simplified pattern which is mapped to the Model#findOneBy{view}({string} key, [{function} callback])
call. It expects one key as a path segment, and it will respond with the first match, or 404 if there are no matches.
Example:
GET /by_slug/a_sample_slug
Saving to the database can be enabled by setting the save
flag.
var myModel = couchDBModel(db, {
restapi: {
save: true
}
});
The request handler doesn't treat PUT
and POST
different. Either of them can be used to create a new document or overwrite an existing one. However, the frontend code should use POST
for new documents and PUT
for modifying existing ones, because the browsers treat the requests different by method.
To save/create a document, send a PUT/POST request with a JSON body to the root path.
Common errors are mapped to the standard HTTP status codes (403, 404, 400), with a custom reason string. If something happens between the library and the database, an 500 Database Error
response is given.
You can set an URL prefix to the REST API. The given URL prefix will be stripped from the path before processing the requests.
var myModel = couchDBModel(db, {
restapi: {
prefix: '/api_root',
}
});
// GET /api_root/ returns all documents
The onRequest
function can be used as an express request handler, but the prefix has to be passed to the model.
var myModel = couchDBModel(db, {
restapi: {
prefix: '/my_api'
}
});
app.use('/my_api', myModel.onRequest);
To run unit tests, you have to set the $COUCHDB_BASE_URL environment variable to a working couchdb instance with administrative privileges.
By default, 'couchdb-model-test' will be deleted and created many times during the tests. You can override the database name with $COUCHDB_DB_NAME.
$ COUCHDB_BASE_URL="http://admin:[email protected]:5984/" npm test
- Allow to initialize a model directly with a URL instead of a database handle
- Allow custom validation functions
Promise support