Sling Academy
Home/Node.js/Mongoose: Defining a schema with nested objects

Mongoose: Defining a schema with nested objects

Last updated: December 30, 2023

Introduction

Mongoose is an Object Document Mapper (ODM) library for MongoDB and Node.js. It manages relationships between data, provides schema validation, and is used to translate between objects in code and the representation of those objects in MongoDB. In this tutorial, you’ll learn how to define a Mongoose schema with nested objects, offering you the ability to create more organized and structured data models reflecting real-world entities with multiple hierarchical layers.

Throughout the guide, we’ll increase complexity gradually, starting from a simple nested object scenario and gradually moving to more complex data structures. By the end, you’ll have a comprehensive understanding of handling nested schemas in Mongoose.

Defining a Simple Nested Schema

Firstly, let’s look at how we might define a basic nested schema in Mongoose. Assume we’re managing a collection of ‘Books’ each belongs to a specific ‘author’ who has a ‘name’ and an ’email’:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const authorSchema = new Schema({
  name: String,
  email: String
});

const bookSchema = new Schema({
  title: String,
  publishedYear: Number,
  author: authorSchema
});

const Book = mongoose.model('Book', bookSchema);

In the code above, we have clearly defined a simple nested schema within our ‘bookSchema’. In a ‘Book’, the ‘author’ field is not a simple property; instead, it is now structured according to the ‘authorSchema’.

Using References: Populating Nested Schemas

Sometimes you’ll want to reference another collection instead of nesting the entire object. This is how relationships are maintained in MongoDB using ObjectId references and the populate feature of Mongoose:

const authorSchema = new Schema({
  _id: Schema.Types.ObjectId,
  name: String,
  email: String
});

const bookSchema = new Schema({
  title: String,
  publishedYear: Number,
  author: { type: Schema.Types.ObjectId, ref: 'Author' }
});

const Author = mongoose.model('Author', authorSchema);
const Book = mongoose.model('Book', bookSchema);

This setup allows us to store a reference to ‘author’ by their ObjectId. And with the use of Mongoose’s ‘populate()’ method, you can fetch the author’s details whenever you query for books. However, for the sake of this tutorial, we’ll focus on defining and using nested objects directly embedded in the parent schema.

Adding Arrays of Nested Objects

Going further, collections might require arrays of nested objects. For instance, if a book can have multiple authors, we can model this as follows:

const bookSchema = new Schema({
  title: String,
  publishedYear: Number,
  authors: [authorSchema]
});

const Book = mongoose.model('Book', bookSchema);

Here, ‘authors’ is an array that can hold multiple ‘authorSchema’ instances. Given that flexibility, each book document can accommodate several authors.

Advanced Nesting and Sub-Documents

In more complex scenarios, you may have multi-level nesting which translates to sub-documents in Mongoose. For example, authors might have addresses with several fields:

const addressSchema = new Schema({
  street: String,
  city: String,
  country: String
});

const authorSchema = new Schema({
  name: String,
  email: String,
  address: addressSchema
});

const bookSchema = new Schema({
  title: String,
  publishedYear: Number,
  authors: [authorSchema]
});

// The rest remains unchanged

Here we have a schema for addresses embedded within our ‘authorSchema’ which in turn is embedded into the ‘bookSchema’. This is a practical approach to sculpting complex, structured data models using Mongoose.

Validating Nested Object Data

Mongoose’s schema system comes equipped with validation rules. You can apply these to nested objects just as you would to top-level schema fields. Consider extending the ‘addressSchema’ to include some basic validations:

const addressSchema = new Schema({
  street: { type: String, required: true },
  city: { type: String, required: true },
  country: { type: String, required: [true, 'Country is required.'] }
});

// The rest of the bookSchema and authorSchema follows these definitions as shown above

By adding ‘required’ validators, Mongoose will ensure the completeness of address data when attempting to save a book document.

Conclusion

In conclusion, defining a schema with nested objects in Mongoose is an excellent way to reflect complex hierarchical data structures. Especially with real-world applications where relationships need concise, organized molds, using nested objects simply make sense. This guide has covered the definitions from simple to advanced levels, validation rules for sub-documents, and more. With the knowledge you’ve gained here, structuring documents in your MongoDB collections will become much more intuitive and maintainable.

Next Article: [Fixed] Mongoose always returns an empty array []

Previous Article: Mongoose: How to compile a model from a schema

Series: Mongoose.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates