Introduction to Mongoose Schemas

Aug 2, 2019

In Mongoose, a schema is a configuration object for a model. Schemas do not allow you to read and write from MongoDB, that's what models are for. But they do:

Schema Paths and Casting

The first parameter to the Schema class constructor is a definition object. This object defines what paths a schema has. For example, the below userSchema has a name path and an age path.

const userSchema = new mongoose.Schema({
  name: String,
  age: Number
});

userSchema.path('name'); // SchemaString { ... }
userSchema.path('age'); // SchemaNumber { ... }

To create a model in Mongoose, you call the mongoose.model() function with a schema as the 2nd parameter. For example, UserModel in the below example will have name and age properties, and will strip out any properties that aren't defined in userSchema.

const userSchema = new mongoose.Schema({
  name: String,
  age: Number
});

const UserModel = mongoose.model('User', userSchema);

const doc = new UserModel({
  name: 'Jean-Luc Picard',
  age: 59,
  rank: 'Captain'
});
doc.name; // 'Jean-Luc Picard'
doc.age; // 59

// undefined, Mongoose strips out `rank` because it isn't in the schema
doc.rank;

Furthermore, Mongoose will cast documents to match the given schema types. This means you can safely pass untrusted data to Mongoose and trust that the data will match your schema.

const UserModel = mongoose.model('User', userSchema);

const doc = new UserModel({
  name: 'Jean-Luc Picard',
  age: '59' // Mongoose will convert this to a number
});
doc.age; // 59
await doc.save();

// Mongoose will convert '60' from a string to a number, even in an update
await UserModel.updateOne({}, { $set: { age: '60' } });

Validation

In addition to casting values, Mongoose also lets you define validation in your schemas. For example, suppose you want to ensure your users have a name. You can make the name property required in your schema as shown below.

const userSchema = new mongoose.Schema({
  // Make `name` required
  name: { type: String, required: true },
  age: Number
});
const UserModel = mongoose.model('User', userSchema);

const doc = new UserModel({ age: 30 });

const err = await doc.save().catch(err => err);
err.message; // Path `name` is required.

Options

The schema constructor takes 2 parameters: definition and options. You can find a complete list of schema options on the Mongoose docs.

For example, the typeKey option lets you configure what key Mongoose looks for to determine if you're defining a nested path. Suppose you wanted to define a nested key named type:

// This is **not** how you define a `nested.type` property
const schema = new mongoose.Schema({
  nested: {
    type: String
  }
});

schema.path('nested'); // SchemaString { ... }
schema.path('nested.type'); // undefined

There are several workarounds for this use case. One is to set the typeKey option as shown below.

// Make Mongoose look for `$type` instead of `type`
const options = { typeKey: '$type' };
const schema = new mongoose.Schema({
  nested: {
    type: String
  },
  otherProperty: {
    $type: String
  }
}, options);

schema.path('nested.type'); // SchemaString { ... }
schema.path('otherProperty'); // SchemaString { ... }

Want to become your team's MongoDB expert? "Mastering Mongoose" distills 8 years of hard-earned lessons building Mongoose apps at scale into 153 pages. That means you can learn what you need to know to build production-ready full-stack apps with Node.js and MongoDB in a few days. Get your copy!

Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Mongoose Tutorials