---
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 | Schemas, Models & Documents
## Learning Goals
After this lesson you will be able to:
- Implement more complex schemas
- Create, read, update and delete documents using Mongoose models
- Manipulate with Mongoose documents
## Schemas
In the previous learning unit, we created a `Cat` model like this:
:::info lecture
Dans notre exemple précédent, nous définission le modèle de chat suivant :
:::
```javascript
mongoose.model('Cat', {
name: String
})
```
:::info lecture
Le 2e paramètre est ce que l'on appelle le schéma. Il nous permet de donner de la structure à nos documents et évite :
- d'ajouter un champ non-défini dans le schéma
- d'oublier un champ obligatoire (nous verrons comment)
- d'utiliser le mauvais type pour un champ
:::
The second argument that we pass is the object that will have all the **fields** that the documents built based on our `Cat` model will have. This example is a simple version of a *Schema*.
In short, a **schema gives our database structure**. It helps us ensure consistency in our database, and we are less likely to:
- **Add fields that shouldn’t exist** (because you did define it in your schema).
- **Forget fields that are required**.
- **Use the wrong type in a field** (i.e., a `number` inside a `date` field or a `boolean` in a `number` field).
Let’s take a look at how to define a Schema by adding some fields to our *Cat* model. Let's create folder `models` and add a file `Cat.js` inside `models` folder. Let's add this code snippet into it:
:::info lecture
Afin de créer des schémas plus avancés, nous allons avoir recours à la class `mongoose.Schema` :
:::
```javascript
// models/Cat.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const catSchema = new Schema({
name : String,
color: String,
age : Number
});
const Cat = mongoose.model('Cat', catSchema);
module.exports = Cat;
```
Notice how we specify our Schema, and then pass it in as the second argument to `mongoose.model`. We will see this in more depth later on.
:::info
To have `Cat` model available in some other file (part of the app), you need to navigate to this file from the place where you are currently. Example:
```javascript
const Cat = require('./models/Cat.js');
```
:::
### Schema Data Types
:::info lecture
Les différents types possibles :
:::
Our schema is just an object with keys for each of the fields’ names and values for each fields’ types. There are many [schema types](https://mongoosejs.com/docs/schematypes.html) available in Mongoose, but here’s a quick list of common types:
- `String` (ex: `Ironhack`)
- `Number` (ex: `42`)
- `Date` (ex: `Date('2020-12-25')`)
- `Boolean` (ex: `true`)
- `Schema.Types.ObjectId`: to store ids of other collections (ex: `Ironhack`) - this one we will use a lot, so keep it in your mind
- `Schema.Types.Mixed`: to store anything
- `Array` or `[]`: to store an array of anything (ex: `['foo', 42]`)
- `[String]`: to store an array of strings (ex: `['foo', 'bar']`)
- `[Number]`: to store an array of numbers (ex: `[42, -6]`)
### Schema default value
:::info lecture
On peut également renseigner une valeur par défaut :
:::
Often we want a default value for some field otherwise, it will be saved empty. For instance, when a user registers on a website and doesn't provide an image, you can set the image to some default one so the user will get the default avatar image. You can accomplish this using the keyword `default`.
```javascript
const userSchema = new Schema({
email : { type: String},
username : { type: String},
avatarUrl: { type: String, default: 'images/default-avatar.png' }
});
```
:::warning lecture
Notez que la valeur est maintenant un objet !
:::
### Schema Data Types Validation
:::info lecture
Les schémas nous permettent de faire de la validation :
:::
Besides specifying the type of the fields or its default value, we can set more detailed validation by defining more properties:
Field property | Possible values | Description
-----------------------------------------------------------------------------------|-------------------------|-----------------------------------------
`type` | `String`, `Number`, ... | Sets a type for the field
[`default`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-default) | Anything | Sets a default value
[`required`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-required) | `true` | Adds a required validator
[`unique`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-unique) | `true` | Declares an unique index
[`enum`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-enum) | An array | Adds an enum validator
[`min`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-min) | A number | Sets a minimum number validator
[`max`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-max) | A number | Sets a maximum number validator
[`minlength`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-minlength) | A number | Sets a minimum length validator
[`maxlength`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-maxlength) | A number | Sets a maximum length validator
[`trim`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-trim) | `true` | Adds a trim setter
[`lowercase`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-lowercase) | `true` | Adds an lowercase setter
[`match`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-match) | A regex | Sets a regex validator
[`validate`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-validate) | An object | Adds a custom validator (see next part)
[`set`](https://mongoosejs.com/docs/api.html#schematype_SchemaType-validate) | A function | Adds a custom setter (see next part)
**Example**
:::info lecture
Un ex concret :
:::
```javascript
const catSchema = new Schema({
name: { type: String, required: true },
age: { type: Number, min: 0, max: 30 },
color: { type: String, enum: ['white', 'black', 'brown'] },
avatarUrl: { type: String, default: 'images/default-avatar.png' },
location: {
address: String,
city: String
},
countryCode: { type: String, match: /^[A-Z]{2}$/ },
created: {
type: Date,
default: Date.now()
}
});
```
### Schema custom validation and custom setter
If you don't have the right validator, Mongoose gives you the opportunity to write your own. For that, you will need to add `validate` and precise a `validator` function and an error `message`.
:::info lecture
Et mĂŞme nos propres validateurs :
:::
```javascript
// Example of custom validation
const userSchema = new Schema({
linkedinProfile: {
type: String,
validate: {
validator: (text) => {
return text.indexOf('https://www.linkedin.com/') === 0;
},
message: "linkedinProfile must start with 'https://www.linkedin.com/'"
}
}
};
```
<div class="skip">
Mongoose also gives you the opportunity to write your **setter**. Every time you will set a field, Mongoose will transform the value by the function specified by `set`.
```javascript
// Example of custom setter
function capitalize (val) {
if (typeof val !== 'string') {
val = '';
}
return val.charAt(0).toUpperCase() + val.substring(1);
}
const userSchema = new Schema({
name: {
type: String,
set: capitalize // <= here we call the setter we defined earlier
}
});
```
</div>
## Mongoose models
### Create
:::info lecture
Comme `new` + `save()`
:::
First, let's see how we can **insert (create)** some documents. If you take a look at the [Mongoose documentation](https://mongoosejs.com/docs/api.html#model_Model.create), you will see that you can use the function `Model.create()`.
Let's see how that looks if we want to create a new user based on the already defined `User` model. For this demo, we'll create `User.js` inside `models` folder:
```javascript
// models/User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: { type: String },
password: { type: String },
job: { type: String }
}, {
timestamps: true
});
const User = mongoose.model('User', userSchema);
module.exports = User;
```
In the following example, we use the `User` model to create a new document:
```javascript
User.create({ name: 'Alice', password:"ironhack2018", job: 'Architect' }, function (err, user) {
if (err) {
console.log('An error happened:', err);
} else {
console.log('The user is saved and its value is: ', user);
}
});
// The same code as above but with a Promise version
User.create({ name: 'Alice', password:"ironhack2018", job: 'Architect' })
.then(user => { console.log('The user is saved and its value is: ', user) })
.catch(err => { console.log('An error happened:', err) });
```
If you want **to save several documents in your database**, you can either use [`create`](https://mongoosejs.com/docs/api.html#model_Model.create) or [`insertMany`](https://mongoosejs.com/docs/api.html#model_Model.insertMany) with an array as a first parameter.
:::info lecture
Avantage : `insertMany`
:::
### Read
:::info lecture
Les paramètres de `.find()` :
:::
To retrieve multiple documents from a database, we can use [`Model.find()`](https://mongoosejs.com/docs/api.html#model_Model.find) and these are some of the specifics of its syntax:
- The *first* parameter is the query represented by **an object**. If you don't put it you will find all the documents of your collection;
- The second parameter is optional and is the projection represented by an object or a string with all the fields to display;
- The third parameter is optional and is the [options](https://mongoosejs.com/docs/api.html#query_Query-setOptions), represented by an object;
- The *last* parameter is the **callback function** executed when the query is finished. If you don't put any function as a parameter, it will return a Promise.
At this point, for us the most important ones are the first and the last.
```javascript
// Please keep in mind that the point here is to understand what is the order
// and posiible structure of the arguments of .find() Mongoose method
let callback = (err, users) => {});
// Find all users and execute the callback
User.find({}, callback);
// Find all users where (the name is 'Alice' and the age is >= 18) and execute the callback
User.find({ name: 'Alice', age: { $gte: 18 }}, callback);
// Find all users where the name is 'Alice' and only selecting the "name" and "age" fields, and afterwards, execute the callback
User.find({ name: 'Alice' }, 'name age', callback);
// Find all users and sort them by name descending
User.find({}, null, {sort: { name: -1 }}, callback);
// Find all users where the name contains 'alice' (insensitive case) and execute the callback
User.find({ name: /alice/i}, callback);
// Promise version
User.find({ name: 'Alice' })
.then(successCallback)
.catch(errorCallback);
```
In the case we want to retrieve only one specific document, we can either use [`findOne`](https://mongoosejs.com/docs/api.html#model_Model.findOne) or [`findById`](https://mongoosejs.com/docs/api.html#model_Model.findById).
```javascript
User.findOne({ name: "Alice" })
.then(successCallback)
.catch(errorCallback);
User.findById("someMongoIdGoesHere129")
.then(successCallback)
.catch(errorCallback);
```
### Update
To update a field, you can either use [`updateMany`](https://mongoosejs.com/docs/api.html#model_Model.updateMany), [`updateOne`](https://mongoosejs.com/docs/api.html#model_Model.updateOne) or [`findByIdAndUpdate`](https://mongoosejs.com/docs/api.html#model_Model._findByIdAndUpdate). In each case, you need to define first the condition (a query or an id) and then the update, like in the next example. We decided here to only use the Promise syntax.
```javascript
// For all users that as "@ironhack\.com" in its email, change the company to "Ironhack"
User.updateMany({ email: /@ironhack\.com/}, { company: "Ironhack" })
.then(successCallback)
.catch(errorCallback);
// For the first "Alice" found, change the company to "Ironhack"
User.updateOne({ name: "Alice"}, { company: "Ironhack" })
.then(successCallback)
.catch(errorCallback);
// For the user with _id "5a3a7ecbc6ca8b9ce68bd41b", increment the salary by 4200
User.findByIdAndUpdate("5a3a7ecbc6ca8b9ce68bd41b", { $inc: {salary: 4200} })
.then(successCallback)
.catch(errorCallback);
```
### Delete
To delete a document, you can either use [`deleteMany`](https://mongoosejs.com/docs/api.html#model_Model.deleteMany), [`deleteOne`](https://mongoosejs.com/docs/api.html#model_Model._deleteOne) or [`findByIdAndRemove`](https://mongoosejs.com/docs/api.html#model_Model._findByIdAndRemove), like in the next example.
```javascript
// Delete all the users that have "@ironhack.com" in their email
User.deleteMany({ email: /@ironhack\.com/})
.then(successCallback)
.catch(errorCallback);
// Delete the first "Alice" found
User.deleteOne({ name: "Alice"})
.then(successCallback)
.catch(errorCallback);
// Delete the user with _id "5a3a7ecbc6ca8b9ce68bd41b"
User.findByIdAndRemove("5a3a7ecbc6ca8b9ce68bd41b")
.then(successCallback)
.catch(errorCallback);
```
### Utils
Mongoose gives also some utils methods. The one you will probably use the most is the [`countDocuments`](https://mongoosejs.com/docs/api.html#model_Model.countDocuments) method, which lets you count the number of documents that match a specific condition.
```javascript
User.countDocuments({ type: 'student' })
.then(count => { console.log(`There are ${count} students`) })
.catch(err => { console.log(err) });
```
## Mongoose Documents
Mongoose gives another way of manipulating data using the *documents*. We can use this approach with:
- Mongoose models, which are constructor classes we can use to create new instances of the model;
- Some methods such as `Model.find()` in its all forms (`.findOne()`, `.findById()`).
On these documents, we can use the method **[`.save()`](https://mongoosejs.com/docs/api.html#document_Document-save)** to save a document in the database. We can use `.save()` for **creating** a new document and **updating** one.
```javascript
// -------------------- CREATE --------------------
// Based on already defined User model, create a user Alice ('Architect')
var myUser = new User({
name: 'Alice',
password: 'ironhack2018',
job: 'Architect'
});
myUser.save() // Create a new user and return a promise
.then(user => { console.log('The user was created') })
.catch(err => { console.log('An error occured', err) });
// -------------------- UPDATE --------------------
// Find the user with id '5a3a7ecbc6ca8b9ce68bd41b' and update its job and salary
User.findById('5a3a7ecbc6ca8b9ce68bd41b')
.then(user => {
user.job = 'Developer';
user.salary += 10000;
return user.save(); // Update the user '5a3a7ecbc6ca8b9ce68bd41b' and return a promise
})
.then(user => { console.log('The user was updated: ' + user )})
.catch(err => { console.log('An error occured:', err) });
```
## Mongoose methods cheat sheet
:::info lecture
Cheatsheet
:::
Model method | Description
-----------------------------------------------------------------------------------------------|--------------------------------------------------------------
[`create`](https://mongoosejs.com/docs/api.html#model_Model.create) | Create document
[`insertMany`](https://mongoosejs.com/docs/api.html#model_Model.insertMany) | Create many documents
[`find`](https://mongoosejs.com/docs/api.html#model_Model.find) | Finds documents
[`findOne`](https://mongoosejs.com/docs/api.html#model_Model.findOne) | Finds one document
[`findById`](https://mongoosejs.com/docs/api.html#model_Model.findById) | Finds a single document by its `_id` field
[`updateOne`](https://mongoosejs.com/docs/api.html#model_Model.updateOne) | Updates one document
[`updateMany`](https://mongoosejs.com/docs/api.html#model_Model.updateMany) | Updates many documents
[`findByIdAndUpdate`](https://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate) | Updates a single document based on its `_id` field
[`deleteOne`](https://mongoosejs.com/docs/api.html#model_Model.deleteOne) | Deletes one document
[`deleteMany`](https://mongoosejs.com/docs/api.html#model_Model.deleteMany) | Deletes many documents
[`findByIdAndRemove`](https://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove) | Removes a single document based on its `_id` field
[`countDocuments`](https://mongoosejs.com/docs/api.html#model_Model.countDocuments) | Counts number of matching documents in a database collection
Document method | Description
-----------------------------------------------------------------------------|-------------------------------------------------------
[`save`](https://mongoosejs.com/docs/api.html#document_Document-save) | Saves this document
[`toObject`](https://mongoosejs.com/docs/api.html#document_Document-toObject) | Converts this document into a plain javascript object
[`toString`](https://mongoosejs.com/docs/api.html#document_Document-toString) | Helper for console.log
<!-- [`exists`](https://mongoosejs.com/docs/api.html#query_Query-exists) | Specifies an `$exists` condition (to find documents that contain a specific field) -->
## Summary
In this lesson, we learned how to build more complex schemas, with different types, validators and setters.
We have also seen Mongoose methods to perform all sorts of operation in the database and to discover new interesting things about them you can take a look into [the Mongoose API documentation](https://mongoosejs.com/docs/api.html).
## Extra Resources
- [The Mongoose API documentation](https://mongoosejs.com/docs/api.html)