What's a NoSQL database again??
And what's an ODM? →
We're using MongoDB as our database and Mongoose as our ODM
What's a document… and what's a collection? →
In MongoDB
What's a schema, model… and object? →
In Mongoose…
Let's try designing movies. What are some properties that a Movie
should have? →
So the director can be:
See mongodb's docs on data modeling.
What are the advantages / disadvantages of each?
A schema is analogous to a collection. We create a schema with the mongoose.Schema
constructor.
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
You may get some deprecation warnings →
DeprecationWarning: Mongoose: mpromise (mongoose's default ...
mongoose.Promise = global.Promise;
DeprecationWarning: open() is deprecated in mongoose...
mongoose.connect
{useMongoClient: true}
Some Schema / model functionality is so common that they're implemented on multiple schemas
So… one useful plug-in is mongoose-url-slugs →
To use:
npm install
require
SchemaName.plugin(...)
to activate the plugin for that schemaLet's try creating a schema for a pizza and toppings. →
{
size: 'medium',
crust: 'thin',
slug: 'medium-thin-2'
toppings: [{name:'mushroom', extra:true}, {name:'peppers'}]
}
Make sure that you have the required modules for connecting to the database… and creating a slug!
npm install --save mongoose mongoose-url-slugs
Require and connect…
const 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');
One way to define relationships is to embed one document in another… →
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)Schemas represent collections (tables). Notice the different ways of specifying the type of a field:
const Topping = new mongoose.Schema({
name: String,
extra: {type: Boolean, default:false}
});
const 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'));
With mongoose, a model allows you to:
Pizza = mongoose.model('Pizza');
const 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;
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!
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);
});
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);
});
});
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);
});
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);
});
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 (let 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!)