Introduction to Mongoose Schemas

MongoDB is one of the most popular NoSQL databases in the world. While it is easy to use and understand compared to SQL databases, it is a schemaless database.

A "Schema" for a database can be compared with the "Class" in object-oriented programming. Just like a class that provides a blueprint for creating objects in a program, a schema provides a blueprint for creating objects (called documents in MongoDB) in a database. In a Node.js application, we can use the Mongoose object data modeling (ODM) tool to define a schema for a MongoDB collection. A Mongoose schema defines the document structure, document properties, default values, validators, static methods, virtuals, etc.

Before we get started with Mongoose schemas, make sure that you have already installed and configured both Node.js and MongoDB (instructions for Mac and Ubuntu). You can then install Mongoose in your Node.js project with the following command:

$ npm install mongoose --save 
Now you can require Mongoose in your application:
const mongoose = require('mongoose') 
Take a look at this article to learn more about getting started with Mongoose in Node.js.

Everything in Mongoose starts with defining a schema. It maps directly to a MongoDB collection and defines the document structure within that collection. Let us look at the below example that defines a schema for the blog post:

const mongoose = require('mongoose') const  Schema > = mongoose const postSchema = new Schema( title: String, author: String, body: String, comments: [ body: String, createdAt: Date >], published:  type: Boolean, default: false >, createdAt:  type: Date, default: Date.now >, meta:  upvotes: Number, bookmarks: Number > >) 

A key in a Mongoose schema can also be assigned a nested schema containing its own keys and types, like the meta property above. It can even be an array of a nested schema like the comments property.

A Mongoose model is a compiled version of the schema definition that maps directly to a single document in the collection. To create a model in Mongoose, you use the mongoose.model() method with the model name as the first parameter and the schema definition as the second parameter:

const Post = mongoose.model('Post', postSchema) 

Now the Post model is ready for querying, creating, updating, and removing documents from the posts collection in MongoDB:

const post = new Post( title: 'Mongoose Introduction', author: 'Atta' >) post.title // Mongoose Introduction post.author // Atta post.published // false 
By default, Mongoose automatically adds the _id property to every schema definition:
const user = new Schema( name: String >) user.path('_id') // ObjectId 

When you create a new document, a unique value is auto-generated by Mongoose and saved in the _id property. However, you can override Mongoose's default _id with your own _id property:

const birdSchema = new Schema( _id: Number, name: String >) const Bird = mongoose.model('Bird', birdSchema) 

Make sure you assign a value to _id when saving the document. Otherwise, Mongoose will refuse to save the document and throw an error:

const bird = new Bird( name: 'Sparrow' >) await bird.save() // Throws "document must have an _id before saving" // Set `_id` field before calling `save()` bird._id = 45 await bird.save() 

Mongoose allows you to define validation constraints in your schema. For example, let us say you want to make sure that every employee must have a name property and a unique ssn property in the employees collection. You can make the name field required , and the ssn filed unique as shown below:

const empSchema = new Schema( name:  type: String, required: true >, ssn:  type: String, unique: true >, age: Number >) const Employee = mongoose.model('Employee', empSchema) const emp = new Employee( age: 45 >) await emp.save() // Throws "Path `name` is required." 

Each instance of a Mongoose model is called a document. Documents have their own built-in instance methods. You can also define your own custom instance methods:

const foodSchema = new Schema( name: String, type: String >) // Define custom method foodSchema.methods.similarFoods = function (cb)  return mongoose.model('Food').find( type: this.type >, cb) > const Food = mongoose.model('Food', foodSchema) 
Now each instance of Food will have access to the similarFoods() method:
const food = new Food( name: 'Pizza', type: 'Fast Food' >) food.similarFoods((err, foods) =>  console.log(foods) >) 
You can also add static methods to your model by using the statics field:
// Define static method foodSchema.statics.findByName = function (name)  return this.find( name: new RegExp(name, 'i') >) > const Food = mongoose.model('Food', foodSchema) const foods = await Food.findByName('Pizza') 

Query helpers are like instance methods but only for Mongoose queries. You can define query helper functions to extend the Mongoose query building API:

// Define query helper foodSchema.query.byName = function (name)  return this.where( name: new RegExp(name, 'i') >) > const Food = mongoose.model('Food', foodSchema) Food.find() .byName('burger') .exec((err, foods) =>  console.log(foods) >) 

With Mongoose, you can define indexes within your schema at the path level or the schema level. Schema-level indexes are usually required when you create compound indexes:

const postSchema = new Schema( title: String, author: String, body: String, tags:  type: [String], index: true // Path level index > >) // Schema level compound index postSchema.index( name: 1, author: -1 >) 

Virtuals are document properties that you can get and set, but they do not get persisted to MongoDB. These properties are commonly used for formatting or combining fields as well as for splitting a single value into multiple values for storage. Let us look at the following user's schema:

const userSchema = new Schema( name:  first: String, last: String >, age: Number >) const User = mongoose.model('User', userSchema) const john = new User( name:  first: 'John', last: 'Doe' >, age: 32 >) console.log(`$john.name.first> $john.name.last>`) // John Doe 

Instead of concatenating first and last names every time, we can want to create a virtual property called fullName that returns the full name:

userSchema.virtual('fullName').get(function ()  return `$this.name.first> $this.name.last>` >) 
Now whenever you access the fullName property, Mongoose will call your virtual method:
console.log(john.fullName) // John Doe 
Take a look at this article to learn more about Mongoose virtuals.

Mongoose uses aliases to get and set another property value while saving the network bandwidth. It allows you to convert a short name of the property stored in MongoDB into a longer name for code readability:

const userSchema = new Schema( n:  type: String, alias: 'name' > >) const User = mongoose.model('User', userSchema) const user = new User( name: 'Alex' >) console.log(user) // console.log(user.n) // Alex user.name = 'John' console.log(user.n) // John 

The Schema constructor takes a second options parameter that you can use to configure the schema. For example, we can disable the _id attribute completely using the < _id: false >schema option:

const userSchema = new Schema(  name: String >,  _id: false > ) const User = mongoose.model('User', userSchema) const user = new User( name: 'Atta' >) console.log(user) // 

You can find a complete list of schema options on the Mongoose documentation. ✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.

You might also like.

  1. How to read a text file into an array using Node.js
  2. How to copy a file using Node.js
  3. How to check if a file contains a string using Node.js
  4. How to find files that match a pattern using Node.js
  5. How to replace a string in a file using Node.js
  6. How to validate an email address in JavaScript