Mongooseのスキーマ、モデル、仮想プロパティ
type、バリデーション、デフォルトオプションを備えたMongooseスキーマを定義し、モデルを作成して仮想プロパティを追加します。
「Mongooseのスキーマ、モデル、仮想プロパティ」はCoddyKit上の無料MongoDB Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはMongoDB Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 MongoDB Academyコースには全4レッスンが含まれています。
このレッスンの一部はまだ翻訳されておらず、英語で表示されています。
What Is Mongoose?
Mongoose is an Object Document Mapper (ODM) for MongoDB and Node.js. It sits on top of the official MongoDB driver and adds a layer of abstraction: schemas for validating document structure, models for database interaction, middleware hooks for pre/post operation logic, and virtuals for computed fields. Mongoose is the most popular MongoDB library in the Node.js ecosystem and is especially productive for server applications with well-defined data models.
// Install Mongoose
// npm install mongoose
const mongoose = require('mongoose');
// Connect to MongoDB
await mongoose.connect(process.env.MONGODB_URI);
console.log('Mongoose connected to MongoDB');Defining a Schema
A Mongoose Schema defines the structure, types, and constraints of documents in a collection. Each key in the schema corresponds to a document field. You can specify type, required, default, min, max, enum, and many other validation options per field. Schemas are the single source of truth for your document structure in a Mongoose application.
const { Schema } = mongoose;
const userSchema = new Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true,
maxlength: 100
},
email: {
type: String,
required: true,
unique: true,
lowercase: true // automatically converts to lowercase before saving
},
age: {
type: Number,
min: [0, 'Age cannot be negative'],
max: 150
},
role: {
type: String,
enum: ['admin', 'user', 'guest'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
}
});Schema Types: Supported Types
Mongoose supports a rich set of schema types. The most common are String, Number, Date, Boolean, Buffer, mongoose.Schema.Types.ObjectId (for references), Array (denoted as [Type]), and Mixed (any value, no type checking). Mongoose also supports nested schemas (sub-documents) by nesting a Schema definition inside another Schema's field definition.
const { Schema } = mongoose;
const { ObjectId } = Schema.Types;
const orderSchema = new Schema({
customerId: { type: ObjectId, ref: 'User', required: true }, // reference to User model
items: [
{
productId: { type: ObjectId, ref: 'Product' },
quantity: { type: Number, min: 1 },
price: Number
}
],
total: Number,
status: { type: String, default: 'pending' },
metadata: Schema.Types.Mixed, // accepts any shape
tags: [String], // array of strings
shippedAt: Date
});Timestamps Option
Passing { timestamps: true } as the second argument to the Schema constructor makes Mongoose automatically manage createdAt and updatedAt fields on every document. createdAt is set once on insertion, and updatedAt is updated on every save. You do not need to set these fields manually—Mongoose handles them transparently. This is a best practice for all production schemas.
const productSchema = new Schema(
{
name: { type: String, required: true },
price: { type: Number, required: true },
category: String,
stock: { type: Number, default: 0 }
},
{ timestamps: true } // adds createdAt and updatedAt automatically
);
// Documents will have:
// { name: 'Laptop', price: 999, createdAt: Date, updatedAt: Date }Creating a Model
A Model is a constructor compiled from a Schema. It provides the interface for querying and writing documents to the collection. Call mongoose.model('ModelName', schema) to create a model—the first argument is the singular name of the collection (Mongoose automatically pluralizes it: 'User' → 'users' collection). Models should be created once and exported as modules.
// Define schema
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
role: { type: String, default: 'user' }
}, { timestamps: true });
// Compile the model
const User = mongoose.model('User', userSchema);
// This creates/uses the 'users' collection
module.exports = User;
// Usage in another file:
// const User = require('./models/user');
// const user = await User.findOne({ email: 'alice@example.com' });Creating Documents With new Model()
Create a new document instance using the model constructor: new User({ name: '...', ... }). This creates an in-memory document object with validation but does NOT save to the database. Call .save() on the instance to persist it, or use the static User.create() shorthand that combines both steps. Mongoose validates the document against the schema before saving and throws a ValidationError if constraints are violated.
// Method 1: new + save (two-step)
const user = new User({
name: 'Alice',
email: 'alice@example.com',
role: 'admin'
});
await user.save(); // validates then saves to 'users' collection
// Method 2: User.create() shorthand
const user2 = await User.create({
name: 'Bob',
email: 'bob@example.com'
});
console.log('Created user ID:', user2._id);
// Method 3: insertMany for bulk
await User.insertMany([
{ name: 'Carol', email: 'carol@example.com' },
{ name: 'Dave', email: 'dave@example.com' }
]);Virtual Properties
Virtuals are computed properties that are not stored in the database but are computed from other fields on the document. They behave like regular document fields in your application code but are never written to MongoDB. Common use cases: combining firstName and lastName into a fullName virtual, computing age from a birthDate field, or creating a public-facing url from an _id.
const personSchema = new mongoose.Schema({
firstName: String,
lastName: String,
birthDate: Date
});
// Virtual: combines firstName and lastName
personSchema.virtual('fullName').get(function () {
return this.firstName + ' ' + this.lastName;
// Use regular function (not arrow function) to access 'this'
});
// Virtual with a setter for convenience
personSchema.virtual('fullName').get(function () {
return this.firstName + ' ' + this.lastName;
}).set(function (v) {
this.firstName = v.split(' ')[0];
this.lastName = v.split(' ')[1];
});
const Person = mongoose.model('Person', personSchema);
const p = new Person({ firstName: 'Alice', lastName: 'Smith' });
console.log(p.fullName); // 'Alice Smith'Including Virtuals in JSON Output
By default, virtuals are not included when converting a document to JSON (e.g., when sending it in an API response). To include them, either set { toJSON: { virtuals: true } } in the schema options or explicitly call doc.toJSON({ virtuals: true }). In Express apps, res.json(doc) calls toJSON() automatically, so setting toJSON: { virtuals: true } in the schema is the cleanest way to always include them.
const userSchema = new mongoose.Schema(
{
firstName: String,
lastName: String
},
{
toJSON: { virtuals: true }, // include virtuals in res.json()
toObject: { virtuals: true } // include virtuals in .toObject()
}
);
userSchema.virtual('fullName').get(function () {
return this.firstName + ' ' + this.lastName;
});
const user = new User({ firstName: 'Alice', lastName: 'Smith' });
console.log(JSON.stringify(user)); // includes 'fullName': 'Alice Smith'Custom Validation in Schemas
Mongoose schemas support custom validator functions per field. The validator function receives the field value and must return true for valid or false (or throw an error) for invalid. You can also provide a custom error message. Custom validators run before .save() and can be asynchronous (useful for database-level uniqueness checks beyond the unique index).
const productSchema = new mongoose.Schema({
name: String,
price: {
type: Number,
required: true,
validate: {
validator: function (v) {
return v > 0; // price must be positive
},
message: props => 'Price must be positive, got ' + props.value
}
},
sku: {
type: String,
validate: {
validator: function (v) {
return /^[A-Z]{2}-\d{4}$/.test(v); // format: AB-1234
},
message: 'SKU must match format AB-1234'
}
}
});Schema Methods and Statics
Schemas support instance methods (available on each document) and static methods (called on the Model class). Instance methods access this for the specific document, making them ideal for document-specific operations like comparing passwords or formatting output. Statics are useful for common queries or factory functions that don't operate on a specific instance.
const userSchema = new mongoose.Schema({ email: String, passwordHash: String });
// Instance method: available on each user document
userSchema.methods.checkPassword = function (candidatePassword) {
return bcrypt.compare(candidatePassword, this.passwordHash);
};
// Static method: called on the User model
userSchema.statics.findByEmail = function (email) {
return this.findOne({ email: email.toLowerCase() });
};
const User = mongoose.model('User', userSchema);
// Usage:
const user = await User.findByEmail('alice@example.com'); // static
const valid = await user.checkPassword('secret'); // instance methodSubdocuments vs Nested Schema Objects
Mongoose distinguishes between embedded subdocuments (defined as an array of schemas) and nested schema objects (a plain schema definition inside a field). Array subdocuments each get their own _id and can be manipulated as individual documents via doc.items.id(subId). Nested objects share the parent document's lifecycle. Use array subdocuments for ordered collections of records (order line items, comments). Use nested objects for one-to-one embedded structures (address, metadata).
// Nested object — no array, no _id per entry
const userSchema = new Schema({
address: {
street: String,
city: String,
zip: String
}
});
// Array subdocuments — each item gets its own _id
const orderSchema = new Schema({
items: [
{
productId: Schema.Types.ObjectId,
quantity: Number,
price: Number
// _id auto-added to each item
}
]
});
// Access subdocument by ID:
const item = order.items.id(someItemId);Quick Check
Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.
Lesson Recap
In this lesson you learned: Mongoose Schemas define document structure, types, and validation rules, Models are compiled from schemas and provide the query/write interface (Model.find(), new Model(), etc.), and virtuals are computed properties that exist in memory but are never stored in MongoDB — useful for derived fields like fullName or url. Next up we explore Mongoose's query API including chaining, .lean(), and comparison with the native driver.
AI チューターと学ぶ JavaScript — 無料
ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。
- コース
- 30
- レッスン
- 120
よくある質問
「Mongooseのスキーマ、モデル、仮想プロパティ」レッスンは無料ですか?
はい。「Mongooseのスキーマ、モデル、仮想プロパティ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、MongoDB Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 MongoDB Academyコースには全4レッスンが含まれています。
「Mongooseのスキーマ、モデル、仮想プロパティ」で何を学びますか?
type、バリデーション、デフォルトオプションを備えたMongooseスキーマを定義し、モデルを作成して仮想プロパティを追加します。 ブラウザで直接実行するハンズオンコードでMongoDB Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
MongoDB Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのMongoDB Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「Mongooseのスキーマ、モデル、仮想プロパティ」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このMongoDB Academyレッスンでコードを書いて実行できますか?
はい。すべてのMongoDB Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 公式Node.jsドライバーによる接続
- Mongooseのスキーマ、モデル、仮想プロパティ
- Mongooseのクエリ、チェーン、Leanドキュメント
- Mongooseミドルウェア:PreとPostフック