Знакомство с Mongoose для MongoDB

1656679576 znakomstvo s mongoose dlya mongodb

Ник Карник

1*uTZXsVta4TwghNobMkZeZg

Mongoose – это библиотека моделирования объектных данных (ODM) для MongoDB и Node.js. Он управляет связями между данными, обеспечивает проверку схемы и используется для перевода между объектами в коде и представлением этих объектов в MongoDB.

0*b5piDNW1dqlkJWKe
Отображение объектов между Node и MongoDB управляется через Mongoose

MongoDB – это база данных документов NoSQL без схем. Это означает, что вы можете хранить в нем документы JSON, и структура этих документов может отличаться, поскольку она не применяется как база данных SQL. Это одно из преимуществ использования NoSQL, поскольку она ускоряет разработку приложений и уменьшает сложность развертывания.

Ниже приведен пример того, как данные хранятся в Mongo против базы данных SQL:

0*rcotALFe2LeebN_y
0*QOKLctlRwxs5uKVo
Документы NoSQL против реляционных таблиц в SQL

Терминологии

Коллекции

«Коллекции» в Mongo эквивалентны таблицам в реляционных базах данных. Они могут хранить несколько документов JSON.

Документы

«Документы» эквивалентны записям или строкам данных в SQL. Хотя строка SQL может ссылаться на данные в других таблицах, документы Mongo обычно совмещают это в документе.

поля

Поля или атрибуты подобны столбцам в таблице SQL.

Схема

Хотя Mongo не содержит схемы, SQL определяет схему через определение таблицы. «Схема» Mongoose – это структура данных документа (или форма документа), которая выполняется через прикладной уровень.

Модели

Модели — это конструкторы высшего порядка, которые принимают схему и создают экземпляр документа, эквивалентного записям в реляционной базе данных.

Начинаем

Монго Монго

Прежде чем начать, давайте настроим Mongo. Вы можете выбрать из один из следующих вариантов (Мы используем вариант №1 для этой статьи):

  1. Загрузите соответствующую версию MongoDB для операционной системы с веб-сайта MongoDB и следуйте инструкциям по установке
  2. Создайте бесплатную подписку на базу данных песочницы на mLab
  3. Установите Mongo с помощью Docker, если вы предпочитаете использовать Docker

Давайте ознакомимся с некоторыми основами Mongoose, реализовав модель, представляющую данные для упрощенной адресной книги.

Я использую Visual Studio Code, Node 8.9 и NPM 5.6. Запустите свою любимую IDE, создайте пустой проект и начнём! Мы будем использовать ограниченный синтаксис ES6 в Node, поэтому мы не будем настраивать Babel.

Установка NPM

Давайте перейдем в папку проекта и инициализируем наш проект

npm init -y

Давайте установим Mongoose и библиотеку проверки с помощью такой команды:

npm install mongoose validator

Вышеуказанная команда установки установит последнюю версию библиотек. Синтаксис Mongoose в этой статье специфичен для Mongoose v5 и далее.

Подключение к базе данных

Создайте файл ./src/database.js под корнем проекта.

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

Ваша строка подключения будет отличаться в зависимости от установки.

let mongoose = require('mongoose');

const server="127.0.0.1:27017"; // REPLACE WITH YOUR DB SERVER
const database="fcc-Mail";      // REPLACE WITH YOUR DB NAME

class Database {
  constructor() {
    this._connect()
  }
  
_connect() {
     mongoose.connect(`mongodb://${server}/${database}`)
       .then(() => {
         console.log('Database connection successful')
       })
       .catch(err => {
         console.error('Database connection error')
       })
  }
}

module.exports = new Database()

The require(‘mongoose’) вызов выше возвращает объект Singleton. Это означает, что вы звоните впервые require(‘mongoose’), он создает экземпляр класса Mongoose и возвращает его. Во время следующих вызовов он вернет тот же экземпляр, который был создан и возвращен вам впервые из-за того, как импорт/экспорт модуля работает в ES6.

0*RvVsD_byUakUzuCj
Импорт модуля/требуемый рабочий процесс

Подобным образом мы превратили наш класс базы данных в одиночный элемент, вернув экземпляр класса в module.exports поскольку нам нужно только одно подключение к базе данных.

ES6 очень облегчает нам создание шаблона singleton (один экземпляр) из-за того, как работает загрузчик модуля, кэшируя ответ предварительно импортированного файла.

Схема Mongoose против модели

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

Создание модели Mongoose состоит в основном из трех частей:

1. Ссылка на Mongoose

let mongoose = require('mongoose')

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

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

Схема определяет свойства документа через объект, где имя ключа соответствует имени свойства в коллекции.

let emailSchema = new mongoose.Schema({
  email: String
})

Здесь мы определяем свойство под названием по электронной почте с типом схемы Строка который отображается на внутреннем валидаторе, который будет активирован при сохранении в базе данных. Это не удастся, если тип данных не является типом строки.

Разрешаются следующие типы схем:

  • Массив
  • Логический
  • Буфер
  • Дата
  • Смешанный (общий/гибкий тип данных)
  • Номер
  • ObjectId
  • Строка

Mixed и ObjectId определены в require(‘mongoose’).Schema.Types.

3. Экспорт модели

Нам нужно вызвать конструктор модели в экземпляре Mongoose и передать ему название коллекции и ссылку на определение схемы.

module.exports = mongoose.model('Email', emailSchema)

Давайте объединим приведенный выше код в ./src/models/email.js для определения содержимого базовой модели электронной почты:

let mongoose = require('mongoose')

let emailSchema = new mongoose.Schema({
  email: String
})

module.exports = mongoose.model('Email', emailSchema)

Определение схемы должно быть простым, но его сложность обычно зависит от требований программы. Схемы можно повторно использовать, и они могут содержать несколько дочерних схем. В вышеприведенном примере значение свойства email является простым типом значения. Однако это также может быть типом объекта с дополнительными свойствами.

Мы можем создать экземпляр модели, которую мы определили выше, и заполнить его с помощью такого синтаксиса:

let EmailModel = require('./email')

let msg = new EmailModel({
  email: 'ada.lovelace@gmail.com'
})

Давайте усовершенствовать схему электронной почты, чтобы сделать свойство электронной почты уникальным обязательным полем и превратить значение в нижний регистр перед его сохранением. Мы также можем добавить функцию проверки, которая гарантирует, что значение является действительным электронным адресом. Мы будем ссылаться на установленную ранее библиотеку валидатора и использовать ее.

let mongoose = require('mongoose')
let validator = require('validator')

let emailSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,
    unique: true,
    lowercase: true,
    validate: (value) => {
      return validator.isEmail(value)
    }
  }
})

module.exports = mongoose.model('Email', emailSchema)

Основные операции

Mongoose имеет гибкий API и предоставляет многие способы выполнения задания. Мы не будем сосредотачиваться на вариациях, поскольку это выходит за рамки настоящей статьи, но помните, что большинство операций можно выполнять несколькими способами либо синтаксически, либо через архитектуру программы.

Создать запись

Давайте создадим экземпляр модели электронной почты и сохраним его в базе данных:

let EmailModel = require('./email')

let msg = new EmailModel({
  email: 'ADA.LOVELACE@GMAIL.COM'
})

msg.save()
   .then(doc => {
     console.log(doc)
   })
   .catch(err => {
     console.error(err)
   })

Результатом является возвращаемый документ после успешного хранения:

{ 
  _id: 5a78fe3e2f44ba8f85a2409a,
  email: 'ada.lovelace@gmail.com',
  __v: 0 
}

Возвращаются следующие поля (внутренние поля имеют префикс подчеркивания):

  1. The _id поле автоматически генерируется Mongo и является первоначальным ключом коллекции. Его значение является уникальным идентификатором документа.
  2. Значение email поле возвращается. Обратите внимание, что он написан в нижнем регистре, поскольку мы указали lowercase:true атрибут в схеме.
  3. __v это свойство versionKey, установленное для каждого документа при первом создании Mongoose. Его значение содержит внутренняя редакция документа.

Если вы попытаетесь повторить операцию сохранения выше, вы получите ошибку, поскольку мы указали, что поле электронной почты должно быть уникальным.

Получить запись

Давайте попробуем получить запись, которую мы сохранили в базе данных раньше. Класс модели предоставляет несколько статических способов и способов экземпляров для выполнения операций с базой данных. Теперь мы попытаемся найти запись, которую мы создали раньше, используя метод find и передадим электронное письмо в качестве поискового термина.

EmailModel
  .find({
    email: 'ada.lovelace@gmail.com'   // search query
  })
  .then(doc => {
    console.log(doc)
  })
  .catch(err => {
    console.error(err)
  })

Возвращенный документ будет похож на отображаемый при создании записи:

{ 
  _id: 5a78fe3e2f44ba8f85a2409a,
  email: 'ada.lovelace@gmail.com',
  __v: 0 
}

Обновить запись

Давайте изменим запись выше, изменив адрес электронной почты и добавив к нему еще одно поле и все это за одну операцию. Из соображений производительности Mongoose не возвращает обновленный документ, поэтому нам нужно передать дополнительный параметр, чтобы спросить его:

EmailModel
  .findOneAndUpdate(
    {
      email: 'ada.lovelace@gmail.com'  // search query
    }, 
    {
      email: 'theoutlander@live.com'   // field:values to update
    },
    {
      new: true,                       // return updated doc
      runValidators: true              // validate before update
    })
  .then(doc => {
    console.log(doc)
  })
  .catch(err => {
    console.error(err)
  })

Возвращенный документ будет содержать обновленный электронный адрес:

{ 
  _id: 5a78fe3e2f44ba8f85a2409a,
  email: 'theoutlander@live.com',
  __v: 0 
}

Удалить запись

Мы будем использовать findOneAndRemove вызов, чтобы удалить запись. Он возвращает исходный документ, который был удалён:

EmailModel
  .findOneAndRemove({
    email: 'theoutlander@live.com'
  })
  .then(response => {
    console.log(response)
  })
  .catch(err => {
    console.error(err)
  })

Помощники

Мы рассмотрели некоторые базовые функции выше, известные как операции CRUD (создание, чтение, обновление, удаление), но Mongoose также дает возможность настроить несколько типов вспомогательных методов и свойств. Их можно использовать для дальнейшего упрощения работы с данными.

Давайте создадим схему пользователя в ./src/models/user.js с полямиfirstName и lastName:

let mongoose = require('mongoose')

let userSchema = new mongoose.Schema({
  firstName: String,
  lastName: String
})

module.exports = mongoose.model('User', userSchema)

Виртуальная собственность

Виртуальное свойство не сохраняется в базе данных. Мы можем добавить его в нашу схему как помощник для получения и установки значений.

Давайте создадим виртуальную собственность под названием fullName который можно использовать для установки значений firstName и lastName и получить их как комбинированное значение во время чтения:

userSchema.virtual('fullName').get(function() {
  return this.firstName + ' ' + this.lastName
})

userSchema.virtual('fullName').set(function(name) {
  let str = name.split(' ')
  
  this.firstName = str[0]
  this.lastName = str[1]
})

Обратные вызовы для get и set должны использовать ключевое слово function, поскольку нам нужно получить доступ к модели через this ключевое слово. Что изменит использование функций жирной стрелки this ссылается.

Теперь мы можем установить firstName и lastName путем присвоения значения fullName:

let model = new UserModel()

model.fullName="Thomas Anderson"

console.log(model.toJSON())  // Output model fields as JSON
console.log()
console.log(model.fullName)  // Output the full name

Приведенный выше код выведет следующее:

{ _id: 5a7a4248550ebb9fafd898cf,
  firstName: 'Thomas',
  lastName: 'Anderson' }
  
Thomas Anderson

Методы экземпляров

Мы можем создавать собственные вспомогательные методы на схеме и получать доступ к ним через экземпляр модели. Эти методы получат доступ к объекту модели, и их можно использовать достаточно творчески. Например, мы могли бы создать метод для поиска всех людей, имеющих то же имя, что и текущий экземпляр.

В этом примере давайте создадим функцию возврата инициалов для текущего пользователя. Давайте добавим специальный вспомогательный метод под названием getInitials к схеме:

userSchema.methods.getInitials = function() {
  return this.firstName[0] + this.lastName[0]
}

Этот метод будет доступен через экземпляр модели:

let model = new UserModel({
  firstName: 'Thomas',
  lastName: 'Anderson'
})

let initials = model.getInitials()

console.log(initials) // This will output: TA

Статические методы

Подобно методам экземпляров, мы можем создавать статические методы на схеме. Давайте создадим метод для получения всех пользователей в базе данных:

userSchema.statics.getUsers = function() {
  return new Promise((resolve, reject) => {
    this.find((err, docs) => {
      if(err) {
        console.error(err)
        return reject(err)
      }
      
      resolve(docs)
    })
  })
}

Звонок getUsers в классе Model вернет всех пользователей в базе данных:

UserModel.getUsers()
  .then(docs => {
    console.log(docs)
  })
  .catch(err => {
    console.error(err)
  })

Добавление экземпляров и статических методов хороший подход к реализации интерфейса взаимодействия базы данных с коллекциями и записями.

Промежуточное программное обеспечение

Промежуточное программное обеспечение – это функции, выполняемые на определенных этапах конвейера. Mongoose поддерживает промежуточное ПО для следующих операций:

  • Агрегатный
  • документ
  • Модель
  • Запрос

Например, у моделей есть pre и post функции, принимающие два параметра:

  1. Тип события («init», «validate», «save», «remove»)
  2. Обратный вызов, выполняемый с помощью это ссылка на экземпляр модели
0*iZwmyy25FSxuxXlH
Пример промежуточного программного обеспечения (также известного как пре- и пост-хуки)

Давайте попробуем пример, добавив два поля под названием createdAt и updatedAt к нашей схеме:

let mongoose = require('mongoose')

let userSchema = new mongoose.Schema({
  firstName: String,
  lastName: String,
  createdAt: Date,
  updatedAt: Date
})

module.exports = mongoose.model('User', userSchema)

Когда model.save() называется, есть а pre(‘save’, …) и post(‘save’, …) запускаемое событие. Для второго параметра можно передать функцию, которая вызывается при срабатывании события. Эти функции принимают параметр к следующей функции в цепочке промежуточного ПО.

Давайте добавим крючок предварительной сохранности и установим значение для createdAt и updatedAt:

userSchema.pre('save', function (next) {
  let now = Date.now()
   
  this.updatedAt = now
  // Set a value for createdAt only if it is null
  if (!this.createdAt) {
    this.createdAt = now
  }
  
  // Call the next function in the pre-save chain
  next()    
})

Давайте создадим и сохраним нашу модель:

let UserModel = require('./user')

let model = new UserModel({
  fullName: 'Thomas Anderson'
}

msg.save()
   .then(doc => {
     console.log(doc)
   })
   .catch(err => {
     console.error(err)
   })

Вы должны увидеть значение для createdAt и updatedAt когда созданная запись печатается:

{ _id: 5a7bbbeebc3b49cb919da675,
  firstName: 'Thomas',
  lastName: 'Anderson',
  updatedAt: 2018-02-08T02:54:38.888Z,
  createdAt: 2018-02-08T02:54:38.888Z,
  __v: 0 }

Плагины

Предположим, что мы хотим отследить, когда запись была создана и последнее обновление для каждой коллекции в нашей базе данных. Вместо повторения описанного выше процесса, мы можем создать плагин и применить его к каждой схеме.

Давайте создадим файл ./src/model/plugins/timestamp.js и воспроизведите приведенные выше функции как модуль для повторного использования:

module.exports = function timestamp(schema) {

  // Add the two fields to the schema
  schema.add({ 
    createdAt: Date,
    updatedAt: Date
  })

  // Create a pre-save hook
  schema.pre('save', function (next) {
    let now = Date.now()
   
    this.updatedAt = now
    // Set a value for createdAt only if it is null
    if (!this.createdAt) {
      this.createdAt = now
    }
   // Call the next function in the pre-save chain
   next()    
  })
}

Чтобы использовать этот плагин, мы просто передаем его к схемам, которым следует предоставить эту функцию:

let timestampPlugin = require('./plugins/timestamp')

emailSchema.plugin(timestampPlugin)
userSchema.plugin(timestampPlugin)

Построение запроса

Mongoose имеет очень богатый API, который обрабатывает многие сложные операции, поддерживаемые MongoDB. Рассмотрим запрос, в котором мы можем постепенно создавать компоненты запроса.

В этом примере мы собираемся:

  1. Найти всех пользователей
  2. Пропустить первые 100 записей
  3. Ограничьте результаты до 10 записей
  4. Отсортируйте результаты по полю firstName
  5. Выберите имя
  6. Выполните этот запрос
UserModel.find()                   // find all users
         .skip(100)                // skip the first 100 items
         .limit(10)                // limit to 10 items
         .sort({firstName: 1}      // sort ascending by firstName
         .select({firstName: true} // select firstName only
         .exec()                   // execute the query
         .then(docs => {
            console.log(docs)
          })
         .catch(err => {
            console.error(err)
          })

Закрытие

Мы едва поцарапали поверхность, исследуя некоторые из возможностей Mongoose. Это богатая библиотека, наполненная полезными и мощными функциями, благодаря которым работать с моделями данных на прикладном уровне становится настоящим наслаждением.

Хотя вы можете взаимодействовать с Mongo непосредственно с помощью Mongo Driver, Mongoose упростит это взаимодействие, позволяя моделировать связи между данными и легко проверять их.

Смешной факт: Мангуст создается Валерий Карпов который является невероятно талантливым инженером! Он ввел этот термин Стек MEAN.

Если эта статья была полезной,??? и следите за мной в Twitter.

1*278_8HmTEdaRAqFYUemQvQ
Вам также может понравиться мой семинар на youtube: Как создать API REST с помощью Node | Экспресс | Монго

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *