---
tags: ironhack, lecture,
---
<style>
.markdown-body img[src$=".png"] {background-color:transparent;}
.alert-info.lecture,
.alert-success.lecture,
.alert-warning.lecture,
.alert-danger.lecture {
box-shadow:0 0 0 .5em rgba(64, 96, 85, 0.4); margin-top:20px;margin-bottom:20px;
position:relative;
ddisplay:none;
}
.alert-info.lecture:before,
.alert-success.lecture:before,
.alert-warning.lecture:before,
.alert-danger.lecture:before {
content:"👨🏫\A"; white-space:pre-line;
display:block;margin-bottom:.5em;
/*position:absolute; right:0; top:0;
margin:3px;margin-right:7px;*/
}
b {
--color:yellow;
font-weight:500;
background:var(--color); box-shadow:0 0 0 .35em var(--color),0 0 0 .35em;
}
.skip {
opacity:.4;
}
</style>

# Mongoose | Introduction
## Learning Goals
After this lesson you will be able to:
- Identify a web design pattern called MVC
- Explain what an ODM is and use it with Express
- Install and make essential use of Mongoose, an ODM for Node
- Implement a *schema* for your data models
## Web Design Patterns
### Request / Response Cycles
:::info lecture
Problématique de persistence des données...
:::
So far, we've used a few different patterns in making a request from the browser and receiving a response in our server. The server can respond with plain text, static HTML file, dynamic HTML files...
For example, we can display a list of cities using a simple array like in the following code.
```
Web Address: http://localhost:3000/city-list
```
:::info lecture
Nous savons rendre un template et lui passer des données :
:::
```javascript=
app.get('/city-list', (req, res) => {
let cities = ['Miami', 'Madrid', 'Barcelona'];
res.render('cities/list', {
cities: cities
});
});
```
```htmlmixed
<!doctype html>
<html>
<head>
<title>List of cities worked</title>
</head>
<body>
{{#each cities}}
{{this}} <!-- "this" represents each string in the array of cities -->
{{/each}}
</body>
</html>
```
Our problem here is that we can't add new data to our list. For sure we could push more cities to our cities array, but what happens when we restart our server?
***They will disappear!***
We have to *persist* (save) our data so that it stays around even when the server stops. Let's see how we can integrate MongoDB into our Express applications.
:::info lecture
Ces données `cities` sont ici "hardcodées" et même si nous `push`ions des éléments dans notre tableau `cities`, ils seraient perdus au redémarrage du serveur (mémoire vive).
:::
### The MVC Pattern
:::info lecture
Découplage à l'aide du pattern Model-Vue-Controlleur :
:::
To solve this problem, we can use the pattern MVC (*Model-View-Controller*) that adds the Model in such a way that:
- Every time a request is made on the server, a *Controller* handles it.
- This *Controller* will communicate with *Models*.
- *Models* will read and write data directly to a database.
- When the *Controller* has all information, it can pass that data to the *View*.
- The *View* generates a *Response* which is a HTML page rendered to a client (in a browser).
:::info lecture
- chaque requete entrante arrive sur le controlleur
- c'est le model qui échange avec la DB
- le controlleur est le chef d'orchestre : c'est lui qui passe les datas récupérées par le `Model` à la `View`
:::

### Example of MVC Pattern
:::info lecture
Exemple d'implémention :
:::
```javascript=
router.get("/product-list", (req, res, next) => { // <--- this CONTROLLER is...
Product.find() // ... asking for data from the Product MODEL and ...
.then(productsFromDB => {
const data = { productsFromDB };
res.render("products/list", data); // ... sending a VIEW to the client
})
.catch(error => next(error));
});
```
:::info lecture
- L1 : la fonction de callback est notre controlleur
- L2 : le modèle interroge la DB (nous verrons juste après ce qu'est `Product`)
- L5 : la vue est rendue avec nos données récupérées
:::
Instead of the data being hard-coded into our route, it's now dynamic and persists in the database.
### Limitation
:::info lecture
Dans nos nombreuses interractions avec la DB, nous allons :
:::
At a certain point though, it can become difficult to deal with the raw database driver in web development. Let's take a look at a few problems we have using just our MongoDB database driver with express though:
- **We repeat a lot of code**
:::info lecture
Effectuer de nombreuses **tâches répétitives** :
:::
To interact with the database, we have to <b>open a connection</b>, <b>make a transaction</b>, and then <b>close the connection</b>. This process can become tedious.
- **It's hard to enforce data consistency**
:::info lecture
Etre en charge de la **cohérence des données** : MongoDB étant très flexible à ce niveau
:::
MongoDB is meant to have a flexible design that can change on the fly. This feature can be a gift and a curse, mainly when working with others. By default, we can add and remove whatever we'd like from our database collections.
- **Documents are just *data* objects, there's no methods included**
:::info lecture
Nos documents sont des objets JSON, qui ne disposent que de propriétés/valeurs. Seulement nous allons vouloir disposer de **méthodes utilitaires**, comme `fullName()` :
:::
Let's say we have a *"users"* collection, in which each document has the attributes `firstName` and `lastName`. If we wanted to generate the *full name*, we would have to query for the first and last name and then put them together. We can't attach methods to our data to create a `fullName`.
To avoid all these problems we need **an abstraction on top of our database connection** so we can focus on business logic. This abstraction is called an **ODM**.
:::info lecture
Tous ces rôles vont être assurés par un ODM : Mongoose
:::
## Object Document Mapper (ODM)
:::info lecture
Mongoose va donc être notre boîte à outils pour **intéragir avec la base de données en Node.js**.
<span style="font-size:500%">🧰</span>
:::
The Object Document Mappers (ODM) alleviate some of the problems discussed previously.
:heavy_check_mark: **ODMs translate data from documents in our database, to *objects* in JavaScript.**
Over time, developers found that we often repeat much of the same code in our databases, and this isn't very [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). ODMs give us a set of reusable methods to perform CRUD actions more efficiently with our database, such as querying, creating, updating, and even adding our custom methods.
In addition to this, low-level details such as connecting and disconnecting from the database are handled by the ODM, letting us focus on the things that matter. At some point, we may even have more than one database in our application or different types of databases. The ODM is created to abstract these details away.
Finally, ODMs help us enforce data consistency while remaining flexible. Validations [are a bit tricky with just MongoDB](https://docs.mongodb.com/manual/core/document-validation/), and applying consistency is tricky. Shortly, we'll see how ODMs help to make this a bit easier as well.
ODMs are generic for all the databases and technologies we can use in websites. One of the best ODMs we can use to join MongoDB and Node.js is called **Mongoose**.
## Mongoose

[Mongoose](http://mongoosejs.com/docs/) is an <b>npm package</b>, and an ODM for MongoDB / Node. It comes packaged with all of the benefits we discussed above and more. It helps us write database queries in pure JavaScript, making our lives easier. Let's take a look at a simple example, taken from Mongoose's home page.
### Setup
Mongoose is easy to set up! It's just another npm package. Let's start a new project and install it.
:::info lecture
Mettons en place notre setup d'exemple :
:::
```shell
$ mkdir mongoose-example
$ cd mongoose-example
$ npm init --yes
$ npm install mongoose
$ touch example.js
$ code .
```
### Connecting to the Database
:::info lecture
Première étape : se connecter à notre DB
<span style="font-size:500%">🔌</span>
:::
First, we must require mongoose in our `example.js`:
```javascript=
// example.js
const mongoose = require('mongoose');
mongoose
.connect('mongodb://localhost/exampleApp', {useNewUrlParser: true, useUnifiedTopology: true})
.then(x => console.log(`Connected to Mongo! Database name: "${x.connections[0].name}"`))
.catch(err => console.error('Error connecting to mongo', err));
```
:::info lecture
`exampleApp` est le nom de notre database.
NB : Elle sera **automatiquement créée** par Mongoose si elle n'existe pas déjà.
:::
:::info lecture
Lançons notre programme :
```shell
$ node example.js
Connected to Mongo! Database name: "exampleApp"
```
:::
`exampleApp` is the database we are connecting. Often this will be named after our application, and the environment you're connecting to (local development or production/live).
:::info
:bulb: The *same instance of Mongoose* [is shared](https://en.wikipedia.org/wiki/Singleton_pattern) across your application, meaning that once you require and connect to mongoose one time, any `require('mongoose')` in other files after that will be talking to the `exampleApp` database.
:::
:::info
:bulb: You don't have to create the `exampleApp` database. MongoDB will check if the database exists when it connects to the server. If it doesn't exist, it will create the database and then connect to it.
:::
## First operations with Mongoose
:::info lecture
Maintenant connectés, effectuons les premières opérations de CRUD...
:::
**CRUD** operations are the four basic operations of persisting data (Create, Read, Update, Delete), as you may know. Mongoose gives us a handful of tools to make CRUD operations easy. Let's take a look at an example of our first model: `Cat`.
### Our First Model
:::info
:bulb: The **data model** (or just **model** for short) is the part of your code that directly manages the data, logic, and rules of the application. A _model_ stores data that is retrieved according to commands from the route and displayed in the view.
:::
With Mongoose, we define [models](http://mongoosejs.com/docs/api.html#model-js
), which are **JavaScript constructor functions** that can create objects generated from data of **a specific collection** in our database. Our first model will be the `Cat` model:
:::info lecture
Mongoose introduit la **notion de modèle** : le modèle va décrire ce à quoi nos documents finaux de la base de données ressembleront.
:::
:::info lecture
Par ex, que tous nos chats auront un nom :
:::
```javascript
// example.js
...
const Cat = mongoose.model('Cat', { name: String });
```
:::warning
:bulb: The object passed in as the second argument to `mongoose.model` is called a *Schema*: `{ name: String }`. We will look at schemas in detail later.
:::
We're defining a Model called `Cat` as a constructor function that creates objects with a field of `name`, that has a type of `String`.
When we use the `Cat` model to interact with the database, it will only be interacting with **a collection that shares a name with it**. That collection is the `cats` collection.
### Creating an Instance
Creating an instance of a mongoose model is much like building a regular JavaScript object, except the constructor uses an object as a parameter.
Create a new instance, and give the Cat a name:
:::info lecture
Notre modèle défini, nous allons maintenant pouvoir **créer notre première instance** de chat :
:::
```javascript
const kitty = new Cat({ name: 'Ironhacker' });
```
Out object `kitty` is still a regular JavaScript object that exists in memory. It hasn't been added to the database yet.
Since `Cat` is a mongoose model, `kitty` has a `save()` method that comes from `Cat's` prototype. We can save our cat to the database by simply calling the `save()` method:
:::warning lecture
☝️Une instance n'est qu'un objet en mémoire : il n'est pour l'instant pas persisté dans la DB.
:::
:::info lecture
Pour l'écrire dans la DB, grâce à la méthode `.save()` du modèle :
:::
```javascript
// example.js
...
kitty
.save()
.then(newCat => console.log(`A new cat is created: ${newCat}!`))
.catch(err => console.log(`Error while creating a new cat: ${err}`));
```
:::warning lecture
☝️L'utilisation de `.then()`/`.catch()` trahit une promesse : la méthode `.save` retourne en fait une Promise !!!
:::
Behind the scenes, **`save()` is sending a MongoDB `insertOne` command to the database.** `save()` sends information to the database, saves a new document in the *cats* collection and it's asynchronous.
<div class="skip">
All together now:
```javascript
const mongoose = require('mongoose');
mongoose
.connect('mongodb://localhost/exampleApp', {useNewUrlParser: true})
.then(x => console.log(`Connected to Mongo! Database name: "${x.connections[0].name}"`))
.catch(err => console.error('Error connecting to mongo', err));
const Cat = mongoose.model('Cat', { name: String });
const kitty = new Cat({ name: 'Ironhacker' });
kitty
.save()
.then(newCat => console.log(`A new cat is created: ${newCat}!`))
.catch(err => console.log(`Error while creating a new cat: ${err}`));
```
</div>
:::danger lecture
Si une erreur se produit lors de l'écriture en base, il est primordial que nous (developper) nous en soyons averti. Il est donc essentiel de gérer les erreurs grâce aux `.catch`
:::
If the Cat can't be saved for some reason, `save` will return an error *(`err`)*. Otherwise, the object is now saved in the database. Run your program with node and check the `cats` collection in your database:
:::info lecture
Vérifions dans compass que notre chat existe maintenant :

:::
<div class="skip">
```
$ mongo
> use exampleApp
> db.cats.find()
{ "_id" : ObjectId("586aa1193529b9a27a372276"), "name" : "Ironhacker", "__v" : 0 }
```
</div>
### List all Cats
:::info lecture
Nous savons maintenant créer des documents.
:::
We can use the `find()` static method to retrieve all the cats we have in the database. In Mongoose, the `find()` method is asynchronous and, when used with callbacks, will receive two different parameters:
- A JavaScript object to filter in the collection the results we want to get.
- A Callback that Mongoose will execute when retrieves our query result.
<div class="skip">
```javascript
Cat.find({}, (err, cats) => {
if(err){
console.log(`Error occurred during getting cats from DB: ${err}`);
return;
}
console.log('All the CATS!');
// cats is an array of Cat instances
cats.forEach(cat => console.log(` --> cat: ${cat.name}`));
});
```
</div>
The same code written using promises:
:::info lecture
Voyons comment les lister : grâce à `.find()`
:::
```javascript
Cat
.find()
.then(catsFromDB => {
// catsFromDB is an array of Cat instances
catsFromDB.forEach(oneCat => console.log(` --> cat: ${oneCat.name}`));
})
.catch(err => console.log(`Error occurred during getting cats from DB: ${err}`);)
```
:::info lecture
`.find()` retourne également une Promise.
:::
:::info lecture
`catsFromDB` est un tableau d'instances
:::
Behind the scenes, **Mongoose's `find()` is sending a MongoDB `find` command to the database.**
Because we called the `find()` method using `Cat` model, Mongoose is finding the resulting documents from within the **`cats` collection**.
### Organize your code
:::info lecture
Ré-organisons notre code pour l'exercice de façon à créer 10 chats :
:::
We can wrap these operations in functions as well to make our code more organized, and call it whenever we'd like:
```javascript
const mongoose = require('mongoose');
mongoose
.connect('mongodb://localhost/exampleApp', {useNewUrlParser: true})
.then(x => console.log(`Connected to Mongo! Database name: "${x.connections[0].name}"`))
.catch(err => console.error('Error connecting to mongo', err));
const Cat = mongoose.model('Cat', { name: String });
function addNewCat(catName) {
const kitty = new Cat({ name: catName });
kitty
.save()
.then(newCat => console.log(`A new cat is created: ${newCat}!`))
.catch(err => console.log(`Error while creating a new cat: ${err}`));
}
function showCats() {
console.log('All the CATS!');
Cat
.find()
.then(catsFromDB => {
// catsFromDB is an array of Cat instances
catsFromDB.forEach(oneCat => console.log(` --> cat: ${oneCat.name}`));
})
.catch(err => console.log(`Error occurred during getting cats from DB: ${err}`));
}
function addTenCats(){
for (let i=0; i<10; i++){
addNewCat(`Ironhacker ${i}`);
}
}
addTenCats();
/* We have to wait for our cats to save before displaying them
Remember, it's async */
setTimeout(showCats, 1500);
```
## Mongoose connection events
:::info lecture
Les différents évènements de Mongo
:::
With Mongoose you can track connection events on the databse:
Connection event | Description
---------------------------------------------------|------------------------------------------------------------------------------
`mongoose.connection.on('connected', callback)` | Call `callback` when Mongoose is connected.
`mongoose.connection.on('error', callback)` | Call `callback` when an error happened on connection.
`mongoose.connection.on('disconnected', callback)` | Call `callback` when Mongoose is disconnected.
`process.on('SIGINT', callback)` | Call `callback` just before stopping Node (can be simulated with `<Ctrl>-C`)
:::info
:bulb: You can replace `on` by `once` to execute the callback only the first time the event happens.
:::
**Example**
```javascript
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/exampleApp', {useNewUrlParser: true})
// When successfully connected
mongoose.connection.on('connected', () => console.log('Mongoose default connection open'));
// If the connection throws an error
mongoose.connection.on('error', (err) => console.log(`Mongoose default connection error: ${err}`));
// When the connection is disconnected
mongoose.connection.on('disconnected', () => console.log('Mongoose default connection disconnected'));
// If the Node process ends, close the Mongoose connection
process.on('SIGINT', () => {
mongoose.connection.close(() => {
console.log('Mongoose default connection disconnected through app termination');
process.exit(0);
});
});
```
## Mongoose Promises
<div class="skip">
### Basic example with Promise
Until now, we saw that we can handle Mongoose model methods with callbacks and promises. We used example as an example [`find`](http://mongoosejs.com/docs/api.html#model_Model.find), where we handled the result with a callback and then did the same with promises.
A short recap:
The two following examples do the same.
**Example using a callback**
```javascript
MyModel.find({}, (err, results) => {
if (err) {
console.log(`An error happened: ${err}`);
return;
}
console.log(`These are all found results: ${results}`);
});
```
**Example using the Promise returned by `MyModel.find()`**
```javascript
MyModel.find()
.then(results => console.log(`These are all found results: ${results}`))
.catch(err => console.log(`An error happened: ${err}`));
```
:::info
:bulb: We use Promises with the following syntax:
```javascript
myPromise
.then(successCallback)
.catch(failureCallback)
```
:::
</div>
### Promises All
:::info lecture
Nous pouvons utiliser `Promise.all` pour "attendre" que plusieurs promesses se réalisent.
```javascript
const mongoose = require('mongoose');
mongoose
.connect('mongodb://localhost/exampleApp', {useNewUrlParser: true, useUnifiedTopology: true})
.then(x => console.log(`Connected to Mongo! Database name: "${x.connections[0].name}"`))
.catch(err => console.error('Error connecting to mongo', err))
;
const Cat = mongoose.model('Cat', { name: String });
function addNewCat(catName) {
const kitty = new Cat({ name: catName });
return kitty.save();
}
Promise.all([
addNewCat('Felix'),
addNewCat('Azrael')
]).then(cats => {
console.log('2 cats created');
}).catch(err => console.error(err));
```
:::
Sometimes we will need to wait for more than one Promise to complete to continue our program. When that happens - `Promise.all` is there to help! This method **takes as a parameter an array of promises and returns a promise**.
The following example inserts some students and cities. When done, it displays all the values included and closed the Mongoose connection.
```javascript
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/exampleApp', {useNewUrlParser: true});
let Student = mongoose.model('Student', { firstName: String });
let City = mongoose.model('City', { name: String });
let promise1 = Student.insertMany([{ firstName: 'Alice' }, { firstName: 'Bob' }]);
let promise2 = City.insertMany([{ name: 'Madrid' }, { name: 'Barcelona' }, { name: 'Paris' }]);
Promise.all([promise1, promise2])
.then(values => {
console.log("students and cities have been inserted");
console.log(values);
/*
[ [ { _id: 5a4e462048841e65562c465a, firstName: 'Alice', __v: 0 },
{ _id: 5a4e462048841e65562c465b, firstName: 'Bob', __v: 0 } ],
[ { _id: 5a4e462048841e65562c465c, name: 'Madrid', __v: 0 },
{ _id: 5a4e462048841e65562c465d, name: 'Barcelona', __v: 0 },
{ _id: 5a4e462048841e65562c465e, name: 'Paris', __v: 0 } ] ]
*/
mongoose.connection.close();
})
.catch(err => console.error(err));
```
## Summary
In this learning unit, we have learned a new design pattern called **MVC**. We have also seen what are the **ODMs** and talked about **Mongoose**, an ODM that connects Node.js and MongoDB.
We have covered the main features that mongoose gives to us: what is an schema, and how we can create it, which are the different data types we have in mongoose, and how to set up default values for each one in our models.
In conclusion, Mongoose may seem complicated starting out. Ultimately, its job is to make your life easier! Skill will come with practice. :muscle:
## Extra Resources
- [Mongoose Documentation](http://mongoosejs.com/docs/)
- [ES6 Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)