MongoDB features are great, but how do we use it in NodeJS? There are multiple libraries such as mongodb-native
and mongoose
. We are going to use the latter, because it provides validation, casting, and ORM.
Being schema-free could be handy at times; however, there are cases when we want to maintain the consistency of the data. Mongoose offers such an opportunity to enjoy both strict and loose schema. We are going to learn how to CRUD data on MongoDB through Mongoose.
Schemas are the way through which Mongoose provides data types and validations to the MongoDB documents.
We are going to do the examples in the NodeJS shell directly. Go to the meanshop
folder and run
node (and run mongod
if you have not yet done so). Once in the node shell, run this code, and we'll explain the details subsequently:
var mongoose = require('mongoose'), Schema = mongoose.Schema; mongoose.connect('mongodb://localhost/meanshop_cli'); var ProductSchema = new Schema({ description : String, published : Boolean, stock : { type: Number, min: 0 }, title : { type: String, required: true }, price : { type: Number, required: true }, updated_at : { type: Date, default: Date.now }, }); var Product = mongoose.model('Product', ProductSchema);
Keep the terminal window open while we explain what we just did. In order to connect to MongoDB, we need a URI. We can even have a URI with multiple MongoDB servers in a production environment. Since we are in localhost and using the default port, we do not require a password, username, or port. However, the full URI looks like the following code:
// single servers
var uri = 'mongodb://username:password@hostname:port/database'
// multiple servers in replica sets
var uri = 'mongodb://user:pass@localhost:port/database,mongodb://anotherhost:port,mongodb://yetanother:port';
mongoose.connect(uri);
We pass the URI to mongoose.connect
in order to connect to MongoDB. We can specify a database name that does not exist yet, and it will be created on the fly.
The next step is to define the new schema. As we can see, we have labels and schema types. The following are the valid schema types that we have at our disposal:
String
: This stores a string value encoded in UTF-8Number
: This stores a number valueDate
: This stores a date and time object as an ISODate
Buffer
: This stores binary information, for example, images, files, and so onBoolean
: This stores either true
or false
Mixed
: This stores a JSON object which could contain any kind of elementObjectId
: This is a unique identifier of a record, usually used to refer other documentsArray
: This can hold a collection of any of the other data types described in this listDepending on the type that we define our property, the data will be cast to the specified schema type. Besides the data types, we can set up defaults, validations, setters, and getters as we saw in the preceding example.
The final step is compiling the schema into a model using mongoose.model
. The model is a constructor from where we can create new documents. Go back to the terminal and type the following:
> var glass = new Product({title: 'MEANglass', price: 415.20}); > glass.save(); > console.log(glass); { __v: 0, title: 'MEANglass', price: 415.2, _id: 55c643b50ce1ad8f5bac5642, updated_at: Sat Aug 08 2015 14:00:21 GMT-0400 (AST) }
We just saved a new product! There are some more advanced features that we are going to see later. In the following sections, we are going to use that Product
instance and learn how to CRUD it.
Use Robomongo (http://robomongo.org/) to see the data in MongoDB.
Next, we want to create a new product. There are multiple methods in Mongoose for doing so:
Model#save([options], product,)
: This is the instance method that saves a documentModel.create(doc(s), [fn])
: This is the class method to automatically create and save the data
Product.create
is preferable since it is a shortcut; we can use it like this:
Product.create({title: 'MEANPhone', price: 523.12}, function(err, data){
if(err) console.log(err);
else console.log(data);
});
All the mongoose functions follow an asynchronous pattern like most of the functions in JavaScript. We pass a callback method that tells us when the product was saved or if an error occurred. The other method for creating new data is by using product.save
. We have to first create the product, and then save it:
> var product = new Product({title: 'MEANBook', price: 29.99});
> product.save(function(err){
if(err) console.log(err);
else console.log(product);
});
For convenience's sake, let's create a generic callback method that we can use from now on:
> var callback = function callback(err, docs) {
if (err) console.log('----> Errors: ', err);
else console.log('----> Docs: ', docs);
}
// Now, we can rewrite the save function as follows:
> product.save(callback);
It is time to retrieve our data. Again, Mongoose provides multiple methods for doing so:
Model.find(conditions, [fields], [options], [callback])
Model.findById(id, [fields], [options], [callback])
Model.findOne(conditions, [fields], [options], [callback])
We are going to use Product.find
; the other two, findById
and findOne
, are shortcuts. If we want to get all the products, we can omit the condition in the parameter:
Product.find(callback);
This will get all our products. We can perform searches if we pass conditions, as follows:
Product.find({title: 'MEANBook'}, callback); Product.find({title: /mean/i}, callback); Product.findOne({title: /^mean/i}, callback); Product.find({price: {$gte: 100 }}, callback);
The conditions object is very expressive. We can have regular expressions, and even use aggregators like the following:
$gte
: greater than or equal; and $lte
: less than or equal$eq
: equal; and $ne
: not equal$gt
: greater than; and $lt
: less than$in
: matches in an array; and $nin
: not in arraySimilar to find
methods, we have three methods to update values in a model:
Model.update(conditions, update, [options], [callback])
Model.findByIdAndUpdate(id, [update], [options], [callback])
Model.findOneAndUpdate([conditions], [update], [options], [callback])
However, unlike find
, the update methods are destructive. If we are not careful, we can update (unknowingly) all the documents in a collection. Thus, the callback for multi-document updates is different:
Product.update({}, { price: 0.00 }, { multi: true }, function (err, numberAffected, raw) {
if (err) return handleError(err);
console.log('The number of updated docs was: %d', numberAffected);
console.log('The raw response from Mongo was: ', raw);
});
Oops, we just set all the products' prices to 0.00
! Run Product.find
(callback) and verify the prices. Copy and paste one of the _id
values to update using findByIdAndUpdate
:
Product.findByIdAndUpdate('55567c61938c50c9a339bf86', {price: 100.12}, callback);
Finally, the delete methods are as follows:
Model.remove(conditions, [callback])
Model.findByIdAndRemove(id, [options], [callback])
Model.findOneAndRemove(conditions, [options], [callback])
They are almost identical to the update methods; they are also destructive, so pay attention to the queries/conditions passed. Let's get rid of the first product that has a price of 0.00
:
Product.findOneAndRemove({price: {$eq: 0.00 }}, {price: 149.99}, callback);
By now, we are familiar with all the methods that we need to create a RESTful API in the next chapter. However, before that, let's take a look at some more Mongoose features.
3.148.144.100