MongoDB Schema using Mongoose
What to expect from this article?
- Simple JSON Object Schema.
- What is ObjectId?
- Multilevel JSON Object Schema
- Multilevel JSON Object with an attribute whose schema is not fixed
- Different configurations while defining a Schema
- Referential integrity in MongoDB Collections
- Use of Populate in Mongoose
- Attribute as Primary key or set of attributes as Primary key in a Collection
Simple JSON Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var candidate= new Schema({
email: String,
phone: {type: String,required: true},
location: String,
experience: [{ body: String, date: Date }],
relievingDate: { type: Date, default: Date.now }
});
As you can see, if you want more control over the attribute, use an object while defining the schema of an attribute. Example: phone attribute is of type string and it is a required attribute. If you try to create a document without the phone, then mongoose will throw an error.
email: String is a shorthand notation if you just need to define the data type as String.
The permitted SchemaTypes are:
- String, Number, Date, Boolean, Array, Decimal128
- Buffer -> Used for storing binary data like images, word, txt or pdf files
- ObjectId -> Used to refer documents of another collection
- Map ->New in Mongoose 5.1.0
Example of Map:const homeSchema = new Schema({
footer: {
type: Map,
of: String
}
});
var homeModel= mongoose.model('home', homeSchema);//Let's create a map =>"help" - "www.medium.com/help","contact" - "www.medium.com/contacts"let home = new homeModel{
footer: {
help: "www.medium.com/help",
contact: "www.medium.com/contacts"
}
})// Correct
home.footer.set('career', 'www.medium.com/career');
home.save(;// Bad, the `myspace` property will **not** get saved
home.footer.myspace = 'fail';
let helpLink = home.footer.get('help'));
What is ObjectId?
An ObjectId is a special type typically used for unique identifiers.
const mongoose = require('mongoose');
const bookSchema = new mongoose.Schema({ author: mongoose.Schema.Types.ObjectId });
ObjectId
is a class. However, they are often represented as strings. When you convert an ObjectId to a string using toString()
, you get a 24-character hexadecimal string:
While querying using Object id, make sure you convert string form of Object id to objectId type. Follow below example:
const mongoose = require('mongoose');let idToQuery = "4ecc05e55dd98a436ddcc47c";//convert string to objectId before queryingsomemodel.find({ "_id": mongoose.Schema.Types.ObjectId(idToQuery)});
Different configurations while defining a Schema
Note: Some options work on certain types only
var schema2 = new Schema({
test: {
type: String,
lowercase: true // Always convert `test` to lowercase
trim:true
}
});
lowercase
: boolean, whether to always call.toLowerCase()
on the valueuppercase
: boolean, whether to always call.toUpperCase()
on the valuetrim
: boolean, whether to always call.trim()
on the valuematch
: RegExp, creates a validator that checks if the value matches the given regular expressionenum
: Array, creates a validator that checks if the value is in the given array.minlength
: Number, creates a validator that checks if the value length is not less than the given numbermaxlength
: Number, creates a validator that checks if the value length is not greater than the given number
Options that are available for all schemas:
required
: boolean or function, if true adds a required validator for this propertydefault
: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default.select
: boolean, specifies default projections for queriesvalidate
: function, adds a validator function for this propertyget
: function, defines a custom getter for this property usingObject.defineProperty()
.set
: function, defines a custom setter for this property usingObject.defineProperty()
.alias
: string, mongoose >= 4.10.0 only. Defines a virtual with the given name that gets/sets this path.
Multilevel JSON Object Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var candidate= new Schema({
info:{
personal:{
type: String, lowercase: true, trim: true
}
}
});
Multilevel JSON Object with an attribute whose schema is not fixed
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var candidate= new Schema({
info:{
personal:{}
},
jobDetals:{},
location:{type:String}
});
Referential integrity in MongoDB Collections
We can embed a child object within a parent object using the object id of the child.
const child = new Child({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
const parent= new Parent({
title: 'Casino Royale',
author: child._id // assign the _id from the child
});
As you can see in the above example, the parent has the id of child linked to it and therefore parent will always be linked to the latest form of child document. Now how to get the child document while querying the parent.
Use of Populate in Mongoose
// will give all the attributes of authorparent.findOne({ name: 'Ian Fleming' }).populate('author');
Note: When there’s no document, parent.author
will be null.
If you have an array of authors
in your parentSchema
, populate()
will give you an empty array instead.
What if we only want a few specific fields returned for the populated documents
//Will populate name and ageparent.findOne({ name:'Ian Fleming'}).populate('author','name age');//Will populate only nameparent.findOne({ name:'Ian Fleming'}).populate('author','name');
What if the parent has multiple different children
Parent.
find(...).
populate('publications').
populate('author').
Use chaining in populate for populating multiple child references.
More control while populating child properties
Parent.find({}).populate({ path: 'author', select: 'name' });Parent.
find({}).
populate({
path: 'author',
match: { age: { $gte: 21 } },
// Explicitly exclude `_id`
select: 'name -_id'
}).
Populating across multiple levels using the ref keyword
Example: A User schema with friends attribute that stores objectIds of other users who are friends.
var userSchema = new Schema({
name: String,
friends: [{ type: ObjectId, ref: 'User' }]
});
Populate lets you get a list of a user's friends, but what if you also wanted a user's friends of friends? Specify the populate
option to tell mongoose to populate the friends
array of all the user's friends:
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});
Dynamic References via `refPath`
Mongoose can also populate from multiple collections based on the value of a property in the document. Let’s say you’re building a schema for storing comments. A user may comment on either a blog post or a product.
const commentSchema = new Schema({
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);
The refPath
option is a more sophisticated alternative to ref
. If ref
is just a string, Mongoose will always query the same model to find the populated subdocs. With refPath
, you can configure what model Mongoose uses for each document.
Attribute as Primary key or set of attributes as Primary key in a Collection
var userSchema = new Schema({
name: String,
type: String,
email: String,
phone: String
});
userSchema.index({ phone: 1, email: 1 },{ unique: true, sparse: true});