- [Instructor] Ever since the beginning
of database programming developers are trying
to build abstraction layers to simplify using them.
Especially with object oriented programming,
manipulating the database using rock theories
while everything else is modeled with objects,
feels somewhat unnatural.
For document databases,
this is what an Object Document Mapper, short, ODM does.
And the most frequently used ODM for no chess is Mongoose.
So let's look at the basic example about ODM stoop.
Let's imagine we have a collection that stores cars
and the car is represented by a make, a model and the year.
Now with an ODM, I can create a new instance of car
and pass in these properties as an argument.
The resulting object would then look somewhat like this.
So it's an object with three properties make, model, year
and it also has this save method.
So this save method now contains all the logic
that is needed to actually save
or persist this object to the database.
You might've guessed that something is missing here,
so, where does this class car come from
and where does it get its logic from?
So let's take a step back and let's look
at how you would make this work with Mongoose.
First, we need to define a schema.
A schema contains all fields
and data types of a given object.
Maybe you spot the contradiction here.
MongoDBs had no sequel database
empathy definition schema-less.
But now we define a schema.
This is because, well the underlying database
doesn't need a schema.
Your application usually relies on certain fields
to be present.
Let's imagine you're working on a database system
with your team and some developers
and they start to use vendor instead of make.
MongoDB won't have a problem at all with that.
So you won't get an error,
but you in your code likely will get a problem
because some are rely that there is a field, make
when you, for instance, output that on the website.
By using a schema, you can avoid such problems.
So let's see how we can define a schema
for our car collection.
In Mongoose a schema is defined by mongoose.Schema,
and then we pass in the name of the entity
and then the properties,
the fields that such and car object should have then.
With those fields, you can also define a data type
and as you see for the make, pass in an object
you can not only define the type you can also,
for instance define that this field is required
and you can also define
that it should be indexed in the database.
As you see with the metrics property,
you can also define sub objects there.
So you can really model complex nested documents
using Mongoose.
A schema is like the blueprint for a database object.
To actually create such an object,
we need a so-called model.
And models can be defined rather easily,
you simply say const Car.
So this now creates this car plus equals mongoose.model.
And then you pass in a name
and the schema that should be used for this car object.
So now we can create instances of car.
For instance, I here create my car
and in the end I call myCar.save
and this will then be saved to the database.
Someone else comes along and creates janesCar
and also call save.
Now we have two records in the database.
Under the hood Mongoose uses the MongoDB driver
that we saw before
and it will even create the collection for you.
The collection here would then be named cars.
Lowercase cars,
so it pluralizes automatically the collection.
But the collection ideally is rather transparent for you
when you use Mongoose like that.
So you don't have to care too much
how your collections are named then in the database
at least in theory.
The model of course also comes with methods
to fetch data from the database.
So I can call Car.findOne and pass in a query,
like the model should be Mustang
and then I will get back my Ford Mustang
that I created before.
And then I can manipulate this object
like change a year to 2019, and then I can call save again.
And with that the document will then also be updated
in the database.
One more important feature of Mongoose are static
and instance methods.
You can use them to add new logic
that is very specific to your application.
For instance, if you want to simplify
searching for a car by make,
you can add a new static method called findByMake
and this will then just execute
the respective find method on the database
and will return then this result.
So with this example
you would get all Hondas in the database
by calling car findByMake("Honda").
So as you see here this is a static method
that is defined on car.
So you don't have to have an instance of car
you call this directly on the class.
Sometimes you also want to have methods
that are available for each object instance.
For instance, let's say I want to add a method honk
to each car.
So we'll define this now
and as you see here before we use CarSchema.statics
and now we define it on CarSchema.methods.
And to define a method honk here
which just does a console log.
With that, each instance of car will have a method honk,
so now I can fetch my Ford Mustang
by just calling Car.findOne.
So findOne is also another method
that is by default provided with each instance,
I'll get my Mustang and then I can call honk on it
and the output on the console would then be Ford honks.
So as you see, this is all quite powerful
and that just really touched the surface here
so Mongoose can really do a lot.
And with everything that's pretty powerful,
we also have to be a little bit cautious using them
because there is a lot of magic going on behind the scenes
and you should really be aware
of what you're actually doing.
So one important thing I want you to keep in mind
is you should not put real business logic
with side effects into your model.
For instance, you might feel tempted
to add an instance method sale to your car.
And this method will then do everything
from changing the quantity on stock
to creating an order record
and maybe also mailing the order confirmation to the buyer.
And this is really not what instance
or static methods are for,
they should really not have any side effects.
So you should maybe at max change the collection
they are defined on
but there should not be any logic
that is kind of cross-cutting through your whole system.
This business logic should really be somewhere else
and I will show you later where it should be.
One more thing that is really important is
Mongoose also provides you with a way
to define relations between models
like you could add a field owner to the car
and that would then link to a collection owners
using some ID.
You could then fetch all cars
and use the Mongoose provided method populate
to also fetch the owner of each car.
Differently from relational databases,
where this can be done pretty efficiently
using a chain operation,
a query like this will cause Mongoose to do a find operation
for each record.
This means, if you have result with 1000 documents
that will be one query to fetch all cars
and then 1000 subsequent five queries for each owner.
And I think it's obvious that this will be rather expensive.
And this problem even has a name,
it's the N+1 query problem.
With Mongoose populate,
you can run into this problem rather quickly.
So for instance, if you before just fetched the cars
so your system just does one query
and then later you just add .populate("_owner")
to your query you suddenly have 1001 queries.
It's just one property more.
So you should really be aware
of what's happening under the hood here.
With that all said,
I think we are now ready to jump right into the code.
And as the business logic and database logic for our item
and user management system of our shopping application