MongoDB Academy · Урок

Схемы, модели и виртуальные свойства Mongoose

Вы определите схемы Mongoose с типами, проверкой и параметрами по умолчанию, создадите модели и добавите виртуальные свойства.

Урок 2 из 413 шагов

«Схемы, модели и виртуальные свойства Mongoose» — бесплатный урок MongoDB Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения MongoDB Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс MongoDB Academy содержит 4 уроков всего.

Что такое Mongoose?

Mongoose — это объектно-документный преобразователь (ODM) для MongoDB и Node.js. Он работает поверх официального драйвера MongoDB и добавляет уровень абстракции: схемы для проверки структуры документов, модели для взаимодействия с базой данных, обработчики промежуточного ПО для логики до и после операций, а также виртуальные свойства для вычисляемых полей. Mongoose — самая популярная библиотека MongoDB в экосистеме Node.js; она особенно удобна для серверных приложений с чётко определёнными моделями данных.

// 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');

Определение схемы

Schema Mongoose определяет структуру, типы и ограничения документов в коллекции. Каждому ключу схемы соответствует поле документа. Для каждого поля можно указать тип, обязательность, значение по умолчанию, минимальное и максимальное значения, перечисление и множество других параметров проверки. Схемы являются единственным источником истины для структуры документов в приложении Mongoose.

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
  }
});

Типы схем: поддерживаемые типы

Mongoose поддерживает широкий набор типов схем. Наиболее распространённые из них — String, Number, Date, Boolean, Buffer, mongoose.Schema.Types.ObjectId (для ссылок), Array (обозначается как [Type]) и Mixed (любое значение без проверки типа). Mongoose также поддерживает вложенные схемы (дочерние документы): для этого определение Schema вкладывают в определение поля другой Schema.

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: true } вторым аргументом конструктору Schema заставляет Mongoose автоматически управлять полями createdAt и updatedAt каждого документа. Поле createdAt устанавливается один раз при добавлении, а updatedAt обновляется при каждом сохранении. Вам не нужно задавать эти поля вручную — Mongoose обрабатывает их прозрачно. Это рекомендуемый подход для всех схем в рабочих приложениях.

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 }

Создание модели

Model — это конструктор, скомпилированный из Schema. Он предоставляет интерфейс для запросов и записи документов в коллекцию. Вызовите mongoose.model('ModelName', schema), чтобы создать модель: первый аргумент — имя коллекции в единственном числе (Mongoose автоматически образует множественное число: 'User' → коллекция 'users'). Модели следует создавать один раз и экспортировать как модули.

// 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' });

Создание документов с помощью new Model()

Создайте новый экземпляр документа с помощью конструктора модели: new User({ name: '...', ... }). Это создаёт объект документа в памяти с проверкой, но НЕ сохраняет его в базе данных. Вызовите .save() у экземпляра, чтобы сохранить его, или используйте статическое сокращение User.create(), объединяющее оба шага. Перед сохранением Mongoose проверяет документ по схеме и выдаёт ValidationError, если ограничения нарушены.

// 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' }
]);

Виртуальные свойства

Виртуальные свойства — это вычисляемые свойства, которые не хранятся в базе данных, а вычисляются на основе других полей документа. В коде приложения они ведут себя как обычные поля документа, но никогда не записываются в MongoDB. Распространённые варианты использования: объединение firstName и lastName в виртуальное свойство fullName, вычисление age по полю birthDate или создание общедоступного url на основе _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'

Включение виртуальных свойств в вывод JSON

По умолчанию виртуальные свойства не включаются при преобразовании документа в JSON (например, при отправке в ответе API). Чтобы включить их, задайте { toJSON: { virtuals: true } } в параметрах схемы или явно вызовите doc.toJSON({ virtuals: true }). В приложениях Express вызов res.json(doc) автоматически вызывает toJSON(), поэтому установка toJSON: { virtuals: true } в схеме — самый простой способ всегда включать виртуальные свойства.

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'

Пользовательская проверка в схемах

Схемы Mongoose поддерживают пользовательские функции проверки для каждого поля. Функция проверки получает значение поля и должна вернуть true для допустимого значения или false (либо выдать ошибку) для недопустимого. Также можно указать собственное сообщение об ошибке. Пользовательские проверки выполняются до .save() и могут быть асинхронными (это полезно для проверок уникальности на уровне базы данных, дополняющих уникальный индекс).

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'
    }
  }
});

Методы и статические методы схем

Схемы поддерживают методы экземпляра (доступные для каждого документа) и статические методы (вызываемые у класса Model). Методы экземпляра получают доступ к this конкретного документа, поэтому идеально подходят для операций с отдельными документами, например сравнения паролей или форматирования вывода. Статические методы полезны для распространённых запросов или фабричных функций, не работающих с конкретным экземпляром.

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 method

Дочерние документы и вложенные объекты схемы

Mongoose различает встроенные дочерние документы (определённые как массив схем) и вложенные объекты схемы (обычное определение схемы внутри поля). Каждый дочерний документ в массиве получает собственный _id, и с ним можно работать как с отдельным документом через doc.items.id(subId). Вложенные объекты используют жизненный цикл родительского документа. Используйте дочерние документы в массиве для упорядоченных наборов записей (позиций заказа, комментариев), а вложенные объекты — для встроенных структур «один к одному» (адреса, метаданных).

// 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);

Быстрая проверка

Проверьте, насколько хорошо Вы понимаете концепции MongoDB и баз данных NoSQL из этого урока.

Итоги урока

В этом уроке Вы узнали, что: схемы Mongoose определяют структуру документов, типы и правила проверки, модели компилируются из схем и предоставляют интерфейс для запросов и записи (Model.find(), new Model() и т. д.), а виртуальные свойства вычисляются в памяти, но никогда не сохраняются в MongoDB — они полезны для производных полей, таких как fullName или url. Далее мы рассмотрим API запросов Mongoose, включая цепочки вызовов, .lean() и сравнение с нативным драйвером.

Можно начать бесплатно

Изучай JavaScript с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
30
Уроки
120

Часто задаваемые вопросы

Урок «Схемы, модели и виртуальные свойства Mongoose» бесплатный?

Да — полный текст урока «Схемы, модели и виртуальные свойства Mongoose» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс MongoDB Academy, подпишись на CoddyKit PRO. Курс MongoDB Academy содержит 4 уроков всего.

Чему я научусь в уроке «Схемы, модели и виртуальные свойства Mongoose»?

Вы определите схемы Mongoose с типами, проверкой и параметрами по умолчанию, создадите модели и добавите виртуальные свойства. Ты практикуешь MongoDB Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать MongoDB Academy?

Предыдущий опыт не требуется. MongoDB Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Схемы, модели и виртуальные свойства Mongoose»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке MongoDB Academy?

Да. Каждый урок MongoDB Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Подключение с помощью официального драйвера Node.js
  2. Схемы, модели и виртуальные свойства Mongoose
  3. Запросы Mongoose, цепочки и документы без преобразования
  4. Промежуточное ПО Mongoose: предварительные и последующие хуки
← Назад к MongoDB Academy