CRUDing with Mongoose

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

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-8
  • Number: This stores a number value
  • Date: This stores a date and time object as an ISODate
  • Buffer: This stores binary information, for example, images, files, and so on
  • Boolean: This stores either true or false
  • Mixed: This stores a JSON object which could contain any kind of element
  • ObjectId: This is a unique identifier of a record, usually used to refer other documents
  • Array: This can hold a collection of any of the other data types described in this list

Depending 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.

Tip

Use Robomongo (http://robomongo.org/) to see the data in MongoDB.

Create

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 document
  • Model.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);
});

Tip

Notice the capitalization convention. Instances are in the lower case: product.save(), while the constructors and classes are capitalized: Product.create().

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);

Read

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 array

Update

Similar 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);

Delete

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.144.100