ferret.js is a minimalistic wrapper around the excelent node-mongodb-native driver. It's easy to use and pretty small (the core module is around 300 lines of code, without comments). Ferret's design is centered on:
- Simplicity
- Proper Error Handling and Recovery
- Orthogonality
- Sensible defaults
- Use of common idioms
Ferret is distributed under a MIT license. See the LICENSE file for more information.
ferret.js can be easily installed through NPM:
npm install ferret
var ferret = require('ferret')
ferret.find('users')
.on('each', function(user) {
console.log(user);
})
This is arguably the simplest working program you can write using ferret.js.
The code above fetches all the documents from the collection named users, and prints their contents.
The find function returns an util.EventEmitter instance, to which you can attach event listeners. each is a convenience event that is fired for each result found.
If you want to you can bind to success instead of each to get an array with all the results:
ferret.find('users')
.on('success', function(users) {
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
})
It's probably a good idea to add some error handling to the code. This can be done by attaching a listener to the error event (Notice that the on calls are chainable):
ferret.find('users')
.on('success', function(users) {
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
})
.on('error', function(err) {
// Do something about it
})
You should have noticed that we did not specify ferret which server, port or database to use.
If nothing is specified, it will default to database test on server 127.0.0.1, port 27017 — mongodb's default. To manually specify what you want, use the connect method.
ferret.connect('test', '127.0.0.1', '27017')
ferret.find('users')
.on('success', function(users) {
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
})
.on('error', function(err) {
// Do something about it
})
On the first time you call connect, ferret will create a shared instance. It can later be accessed by using ferret.shared().
ferret.find is actually a shorthand to ferret.shared().find.
If you're going to use multiple databases, you can store the return value of ferret.connect into a variable:
var someDatabase = ferret.connect('test', '127.0.0.1', '27017')
var otherDatabase = ferret.connect('blah', '127.0.0.1', '27017')
someDatabase.find('users')
.on('success', function(users) {
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
})
.on('error', function(err) {
// Do something about it
})
Previously, we specified the collection to operate on through the first argument given to find.
If you want to, you can use the collection method to specify a collection to operate on.
Notice that find will no longer need or take a collection name as an argument:
var someDatabase = ferret.connect('test', '127.0.0.1', '27017')
var users = someDatabase.collection('users')
users.find()
.on('success', function(users) {
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
})
.on('error', function(err) {
// Do something about it
})
You might have noticed that connect will return right away, and that we don't wait until the connection is estabilished to perform queries.
Ferret will buffer the queries performed and wait until it connects to the server for the first time. If the connection is lost after that, ferret will no longer buffer queries, but instead report errors normally.
It's really not necessary, but if you really want to, you can wait until the connection is estabilished by listening to the 'ready' event:
var database = ferret.connect('test', '127.0.0.1', '27017')
database.on('ready', function(){
var users = database.collection('users')
users.find()
.on('success', function(users) {
for (var i = 0; i < users.length; i++) {
console.log(users[i]);
}
})
.on('error', function(err) {
// Do something about it
})
})
.on('error', function(){
// Could not connect to mongodb
})
Since version 0.2, ferret supports modelling:
var User = ferret.model('user', {
name: String,
age: Number,
email: {
$set: function(value) {
// validate email address
}
}
})
User.findOne({ name: 'John' })
.on('success', function(user) {
// Happy birthday!
user.age++
user.save()
.on('error', function(err) {
// Do something about it too
})
})
.on('error', function(err) {
// Do something about it
})
ferret.js was designed with the following goals in mind:
-
Simplicity - Less code means less bugs, which is great. But simplicity is more than that: It means doing things the "obvious" way sometimes. "Clever" code is usually hard to understand, and less flexible.
-
Proper Error Handling and Recovery - Hiding complexity is not the same as hiding the reality.
ferret.jswill attempt to automatically reconnect to mongodb in case of failure. However, it will also notify you about the operations that may fail in the mean time, so you can take the appropriate actions. -
Orthogonality - The same operations are provided on a consistent manner when accessing the database through different means. Ferret currently allows for access through:
- a shared connection object
- manually instanced connections
FerretCollectionobjectsFerretModelobjects (subset of other APIs)
-
Sensible defaults -
ferret.jscomes with sensible defaults built in so that you can get to your application logic up as quickly as possible. If you need a more tailored behavior, you can easily configure things later. -
Use of Common idioms -
ferret.jssticks to the conventions of mongodb and of thenode-mongodb-nativedriver as closely as possible, so you can easily make use of existing tutorials and documentation.
Each ferret instance is actually a state machine described by the following state diagram:
|
V
+-----------+
| start |
+-----------+
/ \
/ \
/ \
/ \
V V
++======================++ ++=============++
|| | || || error ||
|| V || ++=============++
|| +-----------+ ||
|| | connected | ||
|| +-----------+ ||
|| | Λ ||
|| V | ||
|| +--------------+ ||
|| | disconnected | ||
|| +--------------+ ||
|| ||
|| ||
++======================++
ready
ready and error are both final states. connected and disconnected are sub-states of ready.
The current state of an instance can be queried by the Ferret#state() method. The possible return values are:
'start'
'error'
'ready+connected'
'ready+disconnected'
Ferret will only buffer queries in when in the start state.
All queries return a new EventEmitter instance, which will later emit a success or error event, depending on the result.
For convenience, the find query can also emit each and cursor events. each will iterate through the results, and cursor will provide you a raw node-mongodb-native cursor.
You should not listen to both each and success at the same time, as that will not work. The same warning is also valid for cursor and success.
Ferret inherits EventEmitter, and currently emits the following events:
'ready'
'error'
'reconnect'
'disconnect'
This section provides a quick overview of the ferret API. For detailed descriptons of the different commands mongodb provides, please check their documentation.
- Ferret#state() - Returns the instance's current state
- Ferret#find(collection_name[, query[, fields[, options]]]) - Find documents
- Ferret#findOne(collection_name, query) - Find the first document
- Ferret#insert(collection_name, docs) - Inserts one or more documents
- Ferret#save(collection_name, doc) - Inserts if new, updates if existing
- Ferret#update(collection_name, criteria, replacement[, options]) - Updates existing documents
- Ferret#remove(collection_name, criteria) - Removes existing documents
- Ferret#collection(name) - Retuns a
FerretCollectionobject - Ferret#model(schema) - Create a new model
Ferret inherits EventEmitter, so it also provides all functions the latter provides.
- Ferret.connect([database_name[, host[, port]]]) - Creates a new ferret instance
- Ferret.shared(ferret) - Gets (or sets, if
ferretis specified) the shared instance
For convenience, all the functions provided by Ferret instances are also available at the ferret module. When called, these functios will affect the shared instance.
FerretCollection objects can be obtained through the Ferret#collection method. They provide many of the methods the ferret instance provides, minus the collection_name parameter:
- FerretCollection#find([query[, fields[, options]]]) - Find documents
- FerretCollection#findOne(query) - Find the first document
- FerretCollection#insert(docs) - Inserts one or more documents
- FerretCollection#save(doc) - Inserts if new, updates if existing
- FerretCollection#update(criteria, replacement[, options]) - Updates existing documents
- FerretCollection#remove(criteria) - Removes existing documents
Constructors for FerretModel objects can be obtained through the Ferret#model method. FerretModel provides basic modelling functionality, so you can access data on a more object oriented fashion if you want to.
The API is similar to Ferret and FerretCollection, but more limited.
- new FerretModel([data[, options]]) - Create a new model instance
- FerretModel#find([query]) - Find documents and wrap them in models
- FerretModel#findOne(query) - Find the first document and wrap it in a model
- FerretModel#deserialize(data) - Create a model from serialized data. Same as
new FerretModel(data, { deserialize: true }).
- FerretModel#save() - Persist the model back into the database
- FerretModel#remove() - Delete the object from the database
- FerretModel#serialize() - Convert the model to a format ready for storage
- FerretModel#toJSON() - Same as
FerretModel#serialize
Mongoose was already taken ;-)
Yep. With the release of version 0.2.0, ferret now provides models.