mongoose is an orm for MongoDB.
create a new Node project
mkdir my_node_app
cd my_node_app
npm init -y
install mongoose package
npm install mongoose
You need an address (URL) for your mongoDB. When we use Atlas then we get the URL from their website.
It looks like the following:
mongodb+srv://<user>:<pass>@<host>/<db>?<someparams>
Connect to mongoDB
const mongoose = require('mongoose');
const myMongoDBUrl = '<your-mongodb-url>';
mongoose.connect(myMongoDBUrl);
all following queries will wait untill a connection establishes.
Mongoose Terms
- Schema
- define the strucure of your data.
- Model
- a contructor responsible for creating and reading from MongoDB.
- Document
- an instance of a model.
Creating A Schema
easier to do it in a seperate file
User.js
const userSchema = new mongoose.Schema({
name: String,
age: Number
});
// 'User' is the name of the collection
// you are going to see in the DB
module.exports = mongoose.model('User', userSchema);
importing model to index.js
const User = require('./User');
Use The Model
const user = new User({ name: 'nisim', age: 42 });
user.save().then(() => console.log('user saved...'));
async await
const user = new User({ name: 'nisim', age: 42 });
await user.save();
console.log('user saved...');
The create alternative
const user = await User.create({
name: 'nisim',
age: 42
});
console.log('user saved...');
Edit Object
this program won’t edit the new user’s name
const user = await User.create({
name: 'nisim',
age: 42
});
user.name = 'shlomo';
// no change in DB after
save method is required to update DB
const user = await User.create({
name: 'nisim',
age: 42
});
user.name = 'shlomo';
await user.save();
Let’s extend the schema
const userSchema = new mongoose.Schema({
name: String,
age: Number,
email: String,
createAt: Date,
updateAt: Date,
bestFriend: mongoose.SchemaTypes.ObjectId,
hobbies: [String],
address: {
street: String,
city: String,
}
});
Define Seperate Schema For Address
instead of the following
const userSchema = new mongoose.Schema({
name: String,
age: Number,
address: {
street: String,
city: String,
}
});
good for reuse (drier code)
const addressSchema = new mongoose.Schema({
street: String,
city: String,
});
const userSchema = new mongoose.Schema({
name: String,
age: Number,
address: addressSchema
});
errors caused by the schema
that’s fine
const u1 = await User.create({
name: 'nisim',
age: 42
});
process will crash
const u2 = await User.create({
name: 'nisim',
age: 'pita' // not a number!
});
surround with try and catch
try {
const u2 = await User.create({
name: 'nisim',
age: 'pita' // not a number!
});
}
catch (err) {
console.error(err);
}
Validation
validate email
const userSchema = new mongoose.Schema({
name: String,
age: Number,
email: String
});
required
const userSchema = new mongoose.Schema({
name: String,
age: Number,
email: {
type: String,
required: true,
}
});
lowercase
email will be saved in lowercase
const userSchema = new mongoose.Schema({
name: String,
age: Number,
email: {
type: String,
required: true,
lowercase: true,
}
});
Default Value
Let’s define default value to createAt
const userSchema = new mongoose.Schema({
name: String,
age: Number,
createAt: Date,
});
const userSchema = new mongoose.Schema({
name: String,
age: Number,
createAt: {
type: Date,
// not good
default: new Date(),
},
});
const userSchema = new mongoose.Schema({
name: String,
age: Number,
createAt: {
type: Date,
default: () => Date.now(),
},
});
Immutable
no justification for changing the creation time
const userSchema = new mongoose.Schema({
name: String,
age: Number,
createAt: {
type: Date,
default: () => Date.now(),
immutable: true,
},
});
min and max
age can’t be less then 0. so let’s define minimum and maximum.
const userSchema = new mongoose.Schema({
name: String,
age: Number,
});
const userSchema = new mongoose.Schema({
name: String,
age: {
type: Number,
min: 0,
max: 120,
},
});
String min length
const userSchema = new mongoose.Schema({
name: String,
age: Number,
email: {
type: String,
required: true,
lowercase: true,
minLength: 10,
}
});
Custom Validation
only even ages
const userSchema = new mongoose.Schema({
name: String,
age: Number
});
const userSchema = new mongoose.Schema({
name: String,
age: {
type: Number,
validate: {
validator: v => v % 2 === 0,
message: props => `${props.value} not even`,
}
},
});
all validation (built in and custom) run only in case of the methods create and save.
update methods that skip your validations:
- findByIdAndUpdate()
- findOneAndUpdate()
- updateMany()
- updateOne()
- findById
- find
- findOne
- exists
- deleteOne
- deleteMany
The find method is confusing. Therefore the mongoose implement “queries”
await User.where('name').equals('nisim');
await User.where('age').gt(12);
instead of
User.find({ age: { $gte: 21, $lte: 65 } });
we can write
User.where('age').gte(21).lte(65);
Passing query conditions is permitted
User.find().where({ name: 'wonderful' })
Chaining
User.where('age').gte(21).lte(65)
.where('name', 'wonderful')
.where('friends').slice(10)
ref property and populate method
const userSchema = new mongoose.Schema({
name: String,
age: Number,
bestFriend: mongoose.SchemaTypes.ObjectId,
});
add the ref property
const userSchema = new mongoose.Schema({
name: String,
age: Number,
bestFriend: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'User'
}
});
insert data for demo
const p1 = await User.create({
name: 'nisim',
age: 42
});
const p2 = await User.create({
name: 'shlomo',
age: 13
});
p1.bestFriend = p2.id;
nothing special:
await User.where('name').equals('nisim');
return
{
_id: new ObjectId("6422c2697b370569a04a51de"),
name: 'nisim',
age: 42,
bestFriend: new ObjectId("6422c26b7b370569a04a51e1")
}
but with populate
await User.where('name').equals('nisim')
.populate('bestfriend');
return
{
_id: new ObjectId("6422c2697b370569a04a51de"),
name: 'nisim',
age: 42,
bestFriend: {
_id: new ObjectId("6422c26b7b370569a04a51e1"),
name: 'shlomo',
age: 13,
}
}
Progressive mongoose
User.js
const userSchema = new mongoose.Schema({
name: String,
age: Number,
});
Methods
// not arrow function!
userSchema.methods.sayHi = function() {
console.log(`Hi! my name is ${this.name}`);
};
index.js
const p1 = await User.create({ name: 'nisim' });
p1.sayHi();
Static Methods
User.js
userSchema.statics.findByName = function(givenName) {
return this.find({ name: givenName });
};
index.js
const p1 = await User.findByName('nisim');
Define Query
User.js
userSchema.query.byName = function(givenName) {
return this.where('name').equals(givenName);
};
index.js
const p1 = await User.find().byName('nisim');
Virtual
User.js
const userSchema = new mongoose.Schema({
fname: String,
lname: String,
});
userSchema.virtual('fullName').get(function() {
return `${this.fname} ${this.lname}`;
});
DB content
[
{ fname: 'nisim', lname: 'cohen' },
{ fname: 'shlomo', lname: 'levi' },
{ fname: 'david', lname: 'israel' }
]
const [ nisim ] = await User.where('fname')
.equals('nisim');
console.log(nisim.fullName); // output "nisim cohen"