Introduction to Mongoose Schemas
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:
- Define what properties the documents you save in MongoDB can have
- Define custom validation
- Declare virtuals
- Declare getters and setters
- Define statics and methods
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 { ... }