Schemas in TypeScript
Mongoose schemas are how you tell Mongoose what your documents look like. Mongoose schemas are separate from TypeScript interfaces, so you need to define both a document interface and a schema.
import { Schema } from 'mongoose';
// Document interface
interface User {
name: string;
email: string;
avatar?: string;
}
// Schema
const schema = new Schema<User>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
By default, Mongoose does not check if your document interface lines up with your schema.
For example, the above code won't throw an error if email
is optional in the document interface, but required
in schema
.
Defining Middleware
The Mongoose Schema
class in TypeScript has 3 generic parameters:
class Schema<DocType = Document, M extends Model<DocType, any, any> = Model<any, any, any>, SchemaDefinitionType = undefined> extends events.EventEmitter {
// ...
}
The first generic param, DocType
, represents the type that Mongoose uses as this
for document middleware.
For example:
schema.pre('save', function(): void {
console.log(this.name); // TypeScript knows that `this` is a `User` by default
});
Checking Field Names
The 3rd generic param, SchemaDefinitionType
, checks to make sure that every path in your schema is defined in your document interface.
For example, the below code will fail to compile because emaill
is a path in the schema, but not in the SchemaDefinitionType
.
import { Schema, Model } from 'mongoose';
interface User {
name: string;
email: string;
avatar?: string;
}
// Object literal may only specify known properties, but 'emaill' does not exist in type ...
// Did you mean to write 'email'?
const schema = new Schema<User, Model<User>, User>({
name: { type: String, required: true },
emaill: { type: String, required: true },
avatar: String
});
However, Mongoose does **not ** check for paths that are in the document interface, but not in the schema. For example, the below code compiles.
import { Schema, Model } from 'mongoose';
interface User {
name: string;
email: string;
avatar?: string;
createdAt: number;
}
const schema = new Schema<User, Model<User>, User>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
This is because Mongoose has numerous features that add paths to your schema, like timestamps, plugins, etc. without you explicitly putting these paths in the Schema()
constructor.