Mongoose

CSCI-UA.0480-008

NoSQL Database, ODM

What's a NoSQL database again??

  • a database that doesn't model data using tables and relations between those tables
  • instead, usually a key value store
  • or a document store


And what's an ODM?

  • object document mapper
  • maps application objects (objects in your code) to documents in your database
    • allows CRUD operations on those documents


We're using MongoDB as our database and Mongoose as our ODM

Mongoose

mongoose

Vocabulary

What's a document… and what's a collection?

In MongoDB

  • document - a single row or object in your database (like… an instance of a pizza or cat)
  • collection - a group of documents, similar to a table in a relational database

What's a schema, model… and object?

In Mongoose…

  • schema - describes a collection, provides properties and other constraints (think class)
  • model - built from a schema, a constructor that allows you to create objects (think constructor for class)
  • objects/model instances - represent a document

Schemas

A schemas is analogous to a collection. We create a schema with the mongoose.Schema constructor.

  • the convention is that your schema's name will match a lowercase, plural colleciton in your database
  • the Schema constructor takes an object with keys as names of keys that the documents created from this schema will have
  • …and values that represent the configuration of these keys (for example, type)

Models

Once you have a schema, you can then register a model. A model is a constructor for creating objects that represent MongoDB Documents.

Instance Methods

  • save (create a new document)

Static Methods

  • find
  • findOne
  • findOneAndUpdate

Slugs and Plugins (Slug-ins?)

To add extra features to your schemas, you can use plug-ins.

One plug-in, mongoose-url-slugs…

  • can be used to generate a slug (human readable string that's unique for each document) for all of your objects
  • without having to manually specify slug in the schema!

Pizza

Let's try creating a schema for a pizza and toppings.

  • it should allow pizzas to have a size and crust
  • it should associate pizzas with toppings
  • the pizza should have a short-name (a slug)
  • toppings should have a name, and some way of noting whether or not you'd like 'extra' toppings

{
	size: 'medium',
	crust: 'thin',
	slug: 'medium-thin-2'
	toppings: [{name:'mushroom', extra:true}, {name:'peppers'}]
}

Let's Start With Some Setup

Make sure that you have the required modules for connecting to the database… and creating a slug!

  • mongoose
  • mongoose-url-slugs



npm install --save mongoose mongoose-url-slugs

Require and connect…


var mongoose = require('mongoose'),
	URLSlugs = require('mongoose-url-slugs');

// more stuff goes here

mongoose.model('Pizza', Pizza);
mongoose.model('Topping', Topping);
mongoose.connect('mongodb://localhost/pizzadb');

Types / Embedded Documents

  • one way to define relationships is to embed one document in another…
    • for example, this specifies that field Foo contains an Array / list of Bar objects
    • Foo: [Bar]
  • additionally, instead of specifying the type outright, you can use an object that defines some field specifications:
    • type
    • max
    • min
    • required
    • default (for default value)

Your Schema

Schemas represent collections (tables). Notice the different ways of specifying the type of a field:


var Topping = new mongoose.Schema({
	name: String,
	extra: {type: Boolean, default:false}
});

var Pizza = new mongoose.Schema({
	size: {type: String, enum: ['small', 'medium', 'large']},
	crust: String,
	toppings: [Topping]
});

// note that we left out slug from the schema... 
// (the plugin will add it for you!)
// this should go before registering model!
Pizza.plugin(URLSlugs('size crust'));

Models

With mongoose, a model allows you to:

  • create new instances and save them
  • find saved instances (using a static method)
  • update existing instances



Pizza = mongoose.model('Pizza');

Creating and Saving


var pizza1 = new Pizza({
	size: 'small',
	crust: 'thin'
});

pizza1.save(function(err, pizza, count) {
	console.log('made me some pizza', pizza, count, err);
});

// call mongoose.disconnect() in callback function to close
// database connection;

Finding / Retrieving

Ok… just like the commandline client, we can use find:


// find all (try with query/criteria)
Pizza.find(function(err, pizzas, count) {
	console.log(err, pizzas, count);
});

Notice that we get back an Array!

Finding Only One!

But I only want one! Sometimes it's annoying to have to index into an Array if you only want one of something, so there's also findOne


// find only one (returns first)
Pizza.findOne({slug: 'small-2' }, function(err, pizza, count) {
	console.log(err, pizza, count);
});

Finding Then Updating

In Mongoose… instead of using the push operator (like in the commandline client), we have a method, push, that can be called on a property if it represents a list / Array of embedded values:


// update one after finding (hello callbacks!)
Pizza.findOne({slug: 'small-2' }, function(err, pizza, count) {
    // we can call push on toppings!
	pizza.toppings.push({name: 'mushroom'});
	pizza.save(function(saveErr, savePizza, saveCount) {
		console.log(savePizza);	
	});
});

Finding Then Updating Take Two

But of course… we can actually use push in an update query. In this case, we're using findOneAndUpdate to do the find and update all at once!


// find one and update it; maybe better than previous?
// ...notice $push?
Pizza.findOneAndUpdate({slug:'small-2'}, {$push: {toppings: {name:'peppers'}}}, function(err, pizza, count) {
	console.log(err, pizza, count);
});

Finding by Embedded Documents

We can also adjust our query to find by an embedded document. In this case, we use the property of the list of embedded documents… and use another object that describes the embedded document that we'd like to match.


Pizza.find({toppings: {name:'mushroom'}}, function(err, pizzas, count) {
	console.log(pizzas);
});

Finding and Updating Multiple Embedded Documents

Notice that when we update an embedded document, before we save the parent, we have to let mongoose know that we made changes to embedded documents. (shrug)


Pizza.findOne({slug:'small-2'}, function(err, pizza, count) {
	for (var i = 0; i < pizza.toppings.length; i++) {
		pizza.toppings[i].extra = true;
	}
	pizza.markModified('toppings');
	pizza.save(function(err, modifiedPizza, count) {
		console.log(err, modifiedPizza);
	});
});

(whew!)