--- title: 'Учебник Express часть 3: Использование базы данных (с помощью Mongoose)' slug: Learn/Server-side/Express_Nodejs/mongoose translation_of: Learn/Server-side/Express_Nodejs/mongoose ---
В этой статье даётся краткое введение в базы данных, и методика их использования в приложениях Node/Express. Затем мы покажем, как можно использовать Mongoose для доступа к базе данных веб-сайта LocalLibrary. Мы объясним, как объявляются схемы и модели объектов, укажем основные типы полей, и методику базовой валидации. В статье также кратко показаны основные методы доступа к данным модели.
Предварительные сведения: | Express Tutorial Part 2: Creating a skeleton website |
---|---|
Цель: | Уметь спроектировать и создать свои модели, используя Mongoose. |
Сотрудники библиотеки будут использовать сайт Local Library для хранения информации о книгах и абонентах, а абоненты библиотеки будут использовать его для просмотра и поиска книг, для получения информации о доступных копиях, для резервирования или одалживания книг. Чтобы эффективно хранить и извлекать информацию, мы будем хранить её в базе данных.
Express-приложения могут использовать различные базы данных, и есть несколько подходов, которые можно использовать для выполнения операций Create, Read, Update and Delete (CRUD) (создать, прочесть, обновить, удалить). В руководстве дан краткий обзор некоторых доступных опций, и детально рассмотрены некоторые механизмы работы.
Express-приложение может использовать любые базы данных, поддерживаемые Node (сам по себе Express не определяет каких-либо конкретных дополнительных свойств и требований для управления базами данных). Есть много популярных вариантов -- PostgreSQL, MySQL, Redis, SQLite, и MongoDB.
При выборе базы данных следует учитывать такие факторы как время разработки, время обучения, простота репликации и копирования, расходы, поддержка сообщества и т. д. Хотя нет единственной "лучшей" базы данных, почти любое из популярных решений будет приемлемым для сайта малого и среднего размера, такого как наша Local Library.
Более подробно о вариантах смотрите в: Database integration (Express docs).
Существует два подхода при работе с базой данных:
Наилучшую производительность можно получить с помощью SQL или другого языка запросов, поддерживаемого базой данных. Объектные модели (ODM) часто медленнее, потому что требуют перевода объектов в формат базы данных, при этом не обязательно будут использованы наиболее эффективные запросы к базе данных (особенно, если ODM предназначена для различных баз данных и должна идти на большие компромиссы в смысле поддержки тех или иных функций базы данных).
Преимущество применения ORM состоит в том, что программисты могут сосредоточиться на объектах JavaScript, а не на семантике базы данных — особенно, если требуется работать с разными базами данных (на одном или разных веб-сайтах). Они также дают очевидное место для валидации и проверки данных.
Совет: Применение ODM / ORMs часто приводит к снижению затрат на разработку и обслуживание! Если Вы не очень хорошо знакомы с родным языком запросов или если производительность имеет первостепенное значение, следует серьёзно рассмотреть возможность применения ODM.
Есть много ODM/ORM доступных решений на сайте менеджера пакетов NPM (проверьте теги по подгруппе odm и orm).
Популярные решения на момент написания статьи:
Node ORM2 -- это OR менеджер для NodeJS. Поддерживает MySQL, SQLite и Progress, помогает работать с БД, используя объектный подход.
JugglingDB -- это кросс-ДБ ORM для NodeJS, обеспечивающая общий интерфейс для доступа к наиболее популярным форматам БД. Поддерживает MySQL, SQLite3, Postgres, MongoDB, Redis и хранение данных в памяти js (собственный движок, только для тестирования).
Как правило, при выборе решения следует учитывать как предоставляемые функции, так и "деятельность сообщества" ( загрузки, вклад, отчёты об ошибках, качество документации, и т.д. ) . На момент написания статьи Mongoose являлась очень популярной ORM, и разумно, если вы выбрали MongoDB.
В примере LocalLibrary (и до конца раздела) мы будем использовать Mongoose ODM для доступа к данным нашей библиотеки. Mongoose является интерфейсом для MongoDB, NoSQL-базы данных с открытым исходным кодом, в которой использована документов-ориентированная модель данных. В MongoDB «коллекции» и «документы» -- это аналоги «таблиц» и «строк» в реляционных БД.
Это сочетание ODM и БД весьма популярно в сообществе Node, частично потому, что система хранения документов и запросов очень похожа на JSON и поэтому знакома разработчикам JavaScript.
Совет: Не обязательно знать MongoDB, чтобы использовать Mongoose, хотя документацию Mongoose легче использовать и понимать, если вы уже знакомы с MongoDB.
Далее показано, как определить и получить доступ к схеме и моделям Mongoose для примера веб-сайта LocalLibrary.
Прежде чем начинать писать код моделей, стоит обдумать, какие данные нам нужно хранить, и каковы отношения между разными объектами.
Мы знаем, что нужно хранить информацию о книгах (название, резюме (краткое описание), автор, жанр, ISBN (Международный стандартный книжный номер) ) и что может быть несколько доступных экземпляров (с уникальными идентификаторами, статусом наличия и т. д.). Может потребоваться хранить больше информации об авторе (не только имя, т.к. могут быть авторы с одинаковыми или похожими именами). Мы хотим иметь возможность сортировать данные по названиям книг, по авторам, по жанрам и категориям.
При проектировании моделей имеет смысл иметь отдельные модели для каждого «объекта» (группы связанных данных). В этом случае очевидными объектами являются книги, экземпляры книг и авторы.
Можно также использовать модели для представления параметров списка выбора (например, как выпадающий список вариантов), вместо жёсткого кодирования выбора на самом веб-сайте - рекомендуется, когда не все параметры известны или могут быть изменены. Явный кандидат для модели такого типа -- это жанр книги (например, «Научная фантастика», «Французская поэзия» и т.д.),
Как только мы определились с моделями и полями, следует подумать об отношениях между ними.
С учётом сказанного, UML-диаграмма связей (см. ниже) показывает модели, которые представлены как прямоугольники. Мы решили, что создадим модели для книги (общие сведения о книге), для экземпляра книги (состояние отдельных физических копий книги, доступных в системе) и для автора. Кроме того, у нас будет модель для жанра, чтобы эти значения можно было создавать динамически. Решено не создавать модель для BookInstance:status
— мы пропишем в коде необходимые значения, потому что не ожидаем их изменения. На элементах диаграммы показаны имя модели, имена и типы полей, имена методов и типы их результатов .
Также показаны отношения между моделями, включая множественные отношения. Числа на линиях связи показывают максимум и минимум моделей, участвующих отношении. Например, линия между Book
и Genre
показывает, что Book
и Genre
связаны. Числа на этой линии рядом с моделью Book
показывают, что жанр может быть связан с любым количеством книг, а числа на другом конце линии рядом с Genre
отмечают, что книга может быть связана с любым количеством жанров.
Заметка: Как показано в примереMongoose primer ниже, часто лучше иметь поле, определяющее отношение между документами (моделями), только в одной модели (обратное отношение можно найти по присвоенному идентификатору _id
в другой модели). Ниже мы предпочли задать отношения между Book/Genre и между Book/Author в схеме Book, а отношение между Book/BookInstance -- в схеме BookInstance. Этот выбор в некотором смысле был произвольным -- таким же хорошим мог бы быть выбор другого поля в другой схеме.
Заметка: В следующем разделе дан базовый пример, в котором объясняется, как задавать и как использовать модели. При чтении обратите внимание, как будут создаваться модели, приведённые на диаграмме.
В этом разделе кратко описано как подключиться к базе MongoDB с помощью Mongoose, как определить схемы и модели, как сформировать основные запросы.
Примечание: На этот пример значительно повлияли документы Mongoose quick start на npm и официальная документация.
Mongoose устанавливается в ваш проект (package.json) как и другие зависимости - при помощи NPM. Команда установки (выполняется из каталога проекта):
npm install mongoose
Установка Mongoose добавит все зависимости, включая драйвер MongoDB, но не установит саму базу данных. При желании установить сервер MongoDB локально, можно скачать установочный файл здесь для своей операционной системы и установить его. Также можно использовать облако MongoDB.
Примечание: В примере для хранения базы данных мы используем облачный сервис sandbox tier ("песочницу"). This is suitable for development, and makes sense for the tutorial because it makes "installation" operating system independent (database-as-a-service is also one approach you might well use for your production database).
Mongoose требует подключение к MongoDB. Вы можете использовать require() и подключится к локальной БД при помощи mongoose.connect(),
как показано ниже.
// Импортировать модуль mongoose var mongoose = require('mongoose'); // Установим подключение по умолчанию var mongoDB = 'mongodb://127.0.0.1/my_database'; mongoose.connect(mongoDB); // Позволим Mongoose использовать глобальную библиотеку промисов mongoose.Promise = global.Promise; // Получение подключения по умолчанию var db = mongoose.connection; // Привязать подключение к событию ошибки (получать сообщения об ошибках подключения) db.on('error', console.error.bind(console, 'MongoDB connection error:'));
При помощи mongoose.connection
можно получить стандартный объект Connection
. После подключения в экземпляре Connection
возникает событие open (открыт).
Tip: Если необходимо создать дополнительные подключения, можно использовать mongoose.createConnection()
. При этом будут применены те же БД URI (хост, БД, порт, опции и т.д.), что и в connect()
и будет возвращён объект Connection
.
Модели можно создать при помощи интерфейса Schema
. Schema позволяет указать поля, которые будут в каждом документе, значения полей по умолчанию и требования по валидации. Кроме того, можно задать статические методы и методы-хелперы (от help), облегчающие работу с вашими типами данных, а также задать виртуальные свойства, которые можно использовать как и обычные поля, но без влияния на данные в самой базе данных.
Схемы "компилируются " в окончательную модель методом mongoose.model()
. После создания модели её можно использовать для поиска, создания, обновления и удаления объектов данного типа.
Заметка: Каждой модели соответствует коллекция документов в ДБ MongoDB. Документы будут содержать поля тех типов, которые заданы в модели Schema
.
Код ниже показывает, как можно задать простую схему. Сначала при помощи require()
создаётся объект mongoose, затем конструктор Schema создаёт новый экземпляр схемы, при этом различные поля задаются как параметры конструктора.
//Требуется Mongoose var mongoose = require('mongoose'); //Определяем схему var Schema = mongoose.Schema; var SomeModelSchema = new Schema({ a_string: String, a_date: Date });
В примере созданы два поля, типа String и типа Date. В следующем разделе будут примеры полей других типов, их валидации и примеры других методов.
Модели создаются из схем методом mongoose.model()
:
// Определяем схему var Schema = mongoose.Schema; var SomeModelSchema = new Schema({ a_string: String, a_date: Date }); // Компилируем модель из схемы var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Первый аргумент - уникальное имя создаваемой для модели коллекции(Mongoose создаст коллекцию для модели SomeModel), второй аргумент - схема, которая используется для создания модели.
Заметка: После создания классов модели они могут применяться для создания, обновления или удаления записей в базе, для выполнения запросов по всем записям или по их подмножествам. Как это делать, будет показано в разделе Использование моделей, и когда будут создаваться представления.
Схема может содержать любое количество полей, причём каждое поле будет полем документа, хранимого в БД MongoDB. Схема-пример содержит определения многих широко используемых типов полей.
var schema = new Schema( { name: String, binary: Buffer, living: Boolean, updated: { type: Date, default: Date.now }, age: { type: Number, min: 18, max: 65, required: true }, mixed: Schema.Types.Mixed, _someId: Schema.Types.ObjectId, array: [], ofString: [String], // You can also have an array of each of the other types too. nested: { stuff: { type: String, lowercase: true, trim: true } } })
Большинство типов в SchemaTypes (указаны после “type:” или после имён полей) достаточно очевидны. Исключения:
ObjectId
: Представляет отдельный экземпляр модели в БД. Например, book может ссылаться на объект- автора. Поле будет содержать уникальный идентификатор (_id
) отдельного объекта. При необходимости использования этой информации применяют метод populate()
.Код содержит также два способа объявления полей:
name
, binary и
living
).пример:
{ type: String, lowercase: true, trim: true }
)Дополнительная информация - в SchemaTypes (документация Mongoose).
Mongoose предусматривает встроенные валидаторы, валидаторы пользователя, синхронные и асинхронные валидаторы. Во всех случаях можно задать допустимые диапазоны или значения, а также сообщения об ошибках при нарушении условий валидации.
Встроенные валидаторы включают:
Пример ниже (с небольшими изменениями из документации Mongoose) показывает, как задать некоторые валидаторы и сообщения об ошибках:
var breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
required: [true, 'Why no eggs?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea', 'Water',]
}
});
Подробная информация по валидации полей - в разделе Validation (документация Mongoose).
Виртуальные свойства - это свойства документа, которые можно читать (get) и задавать (set), но которые не хранятся в MongoDB. Методы "геттеры" полезны для форматирования и соединения полей, а "сеттеры" применяют для декомпозиции отдельных значений на несколько частей перед сохранением в БД. Пример из документации собирает (и разбирает) виртуальное свойство "полное имя" из полей "имя" и "фамилия", что удобнее, чем конструировать полное имя каждый раз, когда оно используется в шаблоне.
Замечание: В библиотеке виртуальное свойство будет применено для определения уникального URL каждой записи в модели по пути и по значению _id
записи.
Подробная информация - в разделе Virtuals (документация Mongoose).
В схеме можно также задать методы экземпляра (instance methods), статические (static) методы и помощники запросов. Статические методы и методы экземпляра аналогичны, но различаются тем, что методы экземпляра связаны с конкретной записью и имеют доступ к текущему объекту. Помощники запросов позволяют расширить API построителя цепочек запросов (например, можно добавить запрос "byName" ("по имени") в дополнение к методам find()
, findOne()
и findById()
).
Подготовленную схему можно использовать для создания моделей. Модель представляет коллекцию документов в базе данных, в которой можно выполнять поиск, тогда как экземпляры модели представляют отдельные документы, которые можно сохранять и извлекать.
Ниже предлагается краткий обзор. Более подробно смотрите в Models (документация Mongoose).
Чтобы создать запись, следует определить экземпляр модели и вызвать метод save()
. В примере ниже SomeModel -- это модель с единственным полем "name", которую мы создадим из нашей схемы.
// Создать экземпляр модели SomeModel var awesome_instance = new
SomeModel({ name: 'awesome' }); // Сохранить новый экземпляр, передав callback awesome_instance.save(function (err) { if (err) return handleError(err); // сохранили! });
Создание записей (а также обновления, удаления и запросы) - это асинхронные операции, поэтому следует предусмотреть колбэк-функцию, которая будет вызвана при завершении операции. В API используется соглашение о первом аргументе, согласно которому первый аргумент колбэк-функции должен быть значением ошибки (или null). Если API возвращает некоторый результат, он должен быть вторым аргументом.
Можно использовать метод create()
для создании экземпляра модели при его сохранении. Тогда колбэк-функция вернёт ошибку (или null) как первый аргумент и только что созданный экземпляр как второй аргумент.
SomeModel.create({ name: 'also_awesome' }, function (err, awesome_instance) {
if (err) return handleError(err);
// сохранили!
});
Каждая модель ассоциирована с соединением (с соединением по умолчанию, если используется mongoose.model()
). Следует создать новое соединение и вызвать для него .model()
, чтобы создать документ в другой базе данных.
Поля в новой записи могут быть получены и изменены с применением dot (точка)-синтаксиса. Для сохранения изменений служат методы save()
и update()
.
// Доступ к полям модели в dot-нотации console.log(awesome_instance.name
); //вывод в консоль 'also_awesome
' // Изменить запись, модифицируя поля, потом вызвать save().awesome_instance
.name="New cool name";awesome_instance.save(function (err) { if (err) return handleError(err); // сохранили! });
При поиске записей методами запросов, условия поиска следует задавать как документ JSON. Приведённый фрагмент кода (ниже) показывает, как в БД найти имена (name) и возраст (age) всех спортсменов-теннисистов. Соответствие будет определяться по одному полю (sport), но можно добавить критерии поиска, задав, например, регулярное выражение, или удалить все критерии, чтобы получить список всех спортсменов.
var Athlete = mongoose.model('Athlete', yourSchema);
// найти всех теннисистов, выбирать поля 'name' и 'age'
Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) {
if (err) return handleError(err);
// 'athletes' содержит список спортсменов, соответствующих критерию.
})
Если задать колбэк-функцию так, как показано выше, запрос будет выполнен немедленно. Однако колбэк-функция будет вызвана только после завершения поиска.
Заметка: Все колбэк-функции в Mongoose используют образец callback(error, result)
. Если при выполнении запроса возникает ошибка, параметр error
будет содержать объект error, а result
будет null. При успешном запросе параметр error
будет null, а result
будет содержать результат запроса.
Если не задать колбэк-функцию, API вернёт переменную типа Query. Можно использовать объект запроса, чтобы создать и выполнить свой запрос (с колбэк-функцией) позже, при помощи метода exec()
.
// найти всех теннисистов
var query = Athlete.find({ 'sport': 'Tennis' });
// выбрать поля 'name' и 'age'
query.select('name age');
// ограничить результат 5 элементами
query.limit(5);
// сортировать по возрасту
query.sort({ age: -1 });
// выполнить запрос позже
query.exec(function (err, athletes) {
if (err) return handleError(err);
// athletes содержит упорядоченный список из 5 теннисистов
})
Выше условия поиска были заданы в методе find()
. Можно также использовать функцию where()
, кроме того, можно соединить все части в одном запросе применением оператора dot (.) вместо того, чтобы выполнять их раздельно. Фрагмент кода (см. ниже) выполняет тот же запрос, что и предыдущий фрагмент, но с дополнительным условием для возраста.
Athlete.
find().
where('sport').equals('Tennis').
where('age').gt(17).lt(50). //Дополнительное условие
limit(5).
sort({ age: -1 }).
select('name age').
exec(callback); // callback - имя нашей колбэк-функции.
Метод find() находит все записи, удовлетворяющие условию, но часто требуется найти только одну из таких записей. Вот методы для поиска одной записи:
findById()
: Находит документ по заданному идентификатору id
(каждый документ имеет уникальный идентификатор id
).findOne()
: Находит один документ, удовлетворяющий заданному критерию.findByIdAndRemove()
, findByIdAndUpdate()
, findOneAndRemove()
, findOneAndUpdate()
: Находит один документ по id
или по критерию, а затем или обновляет, или удаляет его. Эти методы весьма полезны для обновления или удаления записей.Заметка: Есть также метод count()
, который определяет количество записей, соответствующих условию. Он полезен при выполнении подсчётов без фактического извлечения записей.
Запросы полезны и во многих других случаях. Дополнительная информация - в Queries (документация Mongoose).
Один документ (экземпляр модели) может ссылаться на другой документ при помощи поля ObjectId
схемы, или на много других документов, используя массив идентификаторов ObjectIds
. Идентификатор соответствующей модели хранится в поле. При необходимости получить действительное содержимое связанного документа, следует использовать в запросе метод populate()
, который заменит идентификатор в запросе действительными данными.
Например, в следующей схеме определены авторы и рассказы. У каждого автора может быть несколько рассказов, которые представим массивом ссылок of ObjectId
. У каждого рассказа может быть только один автор. Ссылка "ref" (выделена жирным) указывает в схеме, какая модель должна быть связана с этим полем.
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var authorSchema = Schema({
name : String,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
author : { type: Schema.Types.ObjectId, ref: 'Author' },
title : String
});
var Story = mongoose.model('Story', storySchema);
var Author = mongoose.model('Author', authorSchema);
Можно сохранить ссылки в связанном документе, используя значение идентификатора _id
. Ниже создаётся автор, затем рассказ, и значение идентификатора id автора сохраняется в поле "author" рассказа.
var bob = new Author({ name: 'Bob Smith' });
bob.save(function (err) {
if (err) return handleError(err);
//автор Bob создан, создаём рассказ
var story = new Story({
title: "Bob goes sledding",
author: bob._id // присваиваем полю значение идентификатора Боба. Идентификатор создаётся по умолчанию!
});
story.save(function (err) {
if (err) return handleError(err);
// У Боба теперь есть рассказ!
});
});
Теперь документ "story" ссылается на автора по идентификатору документа "author". Для получения информации об авторе применяется метод populate()
(показано ниже).
Story
.findOne({ title: 'Bob goes sledding' })
.populate('author') //подменяет идентификатор автора информацией об авторе!
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// выводит "The author is Bob Smith"
});
Заметка: Внимательные читатели заметили, что автор добавлен к рассказу, но ничего не сделано, чтобы добавить рассказ к массиву рассказов stories
автора. Как же тогда получить список всех рассказов конкретного автора? Один из возможных вариантов - добавить автора в массив рассказов, но при этом пришлось бы хранить данные об авторах и рассказах в двух местах и поддерживать их актуальность.
Лучше получить _id
нашего автора author, и применить find()
для поиска этого идентификатора в поле "author" всех рассказов.
Story
.find({ author : bob._id })
.exec(function (err, stories) {
if (err) return handleError(err);
// возвращает все рассказы, у которых идентификатор Боба.
});
Это почти все, что следует знать для работы со связанными данными в нашем руководстве. Более полную информацию можно найти в Population (документация Mongoose).
Можно использовать любую структуру файлов при создании схем и моделей, однако мы настоятельно рекомендуем определять каждую схему модели в отдельном модуле (файле), экспортируя метод для создания модели. Пример приведён ниже:
// Файл: ./models/somemodel.js
//Требуется Mongoose
var mongoose = require('mongoose');
//Определяем схему
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string : String,
a_date : Date,
});
//экспортируется функция для содания класса модели "SomeModel"
module.exports = mongoose.model('SomeModel', SomeModelSchema );
You can then require and use the model immediately in other files. Below we show how you might use it to get all instances of the model.
//Создаём модель SomeModel просто вызовом модуля из файла
var SomeModel = require('../models/somemodel')
// Используем объект SomeModel (модель) для поиска всех записей в SomeModel
SomeModel.find(callback_function);
Мы уже немного понимаем, что может делать Mongoose и как следует проектировать модели. Теперь самое время начать работу над сайтом LocalLibrary. Самое первое, что мы должны сделать - установить базу данных MongoDb, в которой будут храниться данные нашей библиотеки.
В этом руководстве мы будем использовать базу данных в "песочнице" ("sandbox") - бесплатный облачный сервис, предоставляемый mLab. Такая база не очень подходит для промышленных веб-сайтов, поскольку не имеет избыточности, но она очень удобна для разработки и прототипирования. Мы используем её, так как она бесплатна, её легко установить, и потому что mLab - популярный поставщик базы данных как сервиса, и это может быть разумным выбором для промышленной базы данных (на данный момент другие известные возможности включают Compose, ScaleGrid и MongoDB Atlas).
Заметка: При желании можно установить БД MongoDb локально, загрузив и установив подходящие для вашей системы двоичные файлы. В этом случае приводимые ниже инструкции не изменятся, за исключением URL базы данных, который нужно будет задать для установки соединения.
Первым делом надо создать аккаунт на mLab (это бесплатно, требует только основных контактных данных и ознакомления с условиями обслуживания).
После входа в систему вы увидите главную страницу home:
Выберите ближайший к Вам регион и щёлкните кнопку Continue.
Откроется экран Final Details для ввода названия БД.
Введите имя новой базы - local_library
и нажмите Continue.
Откроется экран подтверждения заказа Order Confirmation.
Щёлкните Submit Order (подтвердить заказ), чтобы создать БД.
Вы вернётесь на главный (home) экран. Щёлкните по вновь созданной базе, чтобы открыть экран с детальной информацией. Как видно, в БД нет коллекций (данных).
На форме выше обведён URL для соединения с вашей БДthat you need to use to access your database is displayed on the form above (shown for this database circled above). Чтобы его использовать, необходимо создать пользователя БД, который позже введёт этот URL.
Теперь БД создана, и для доступа к ней есть URL, имя пользователя и пароль. Это должно выглядеть примерно так: mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library
.
Откройте окно команд и перейдите в каталог, в котором создан каркас веб-сайта Local Library. Введите команду install, чтобы установить Mongoose (и её зависимости), а также добавьте её в файл package.json, если вы ещё не сделали этого ранее, при чтении примера Mongoose Primer.
npm install mongoose
Откройте /app.js (в корне проекта) и скопируйте приведённый ниже текст, в котором объявляется объект приложения Express (после строки var app = express();
). Замените строку url БД ('insert_your_database_url_here') тем URL, который представляет вашу БД (т.е. используйте информацию, полученную от mLab).
//Устанавливаем соединение с mongoose var mongoose = require('mongoose'); var mongoDB = 'insert_your_database_url_here';//замените url!!! mongoose.connect(mongoDB); mongoose.Promise = global.Promise; var db = mongoose.connection; db.on('error', console.error.bind(console, 'MongoDB connection error:'));
Как указано ранее в примере Mongoose primer, этот код задаёт соединение по умолчанию с привязкой события ошибки error (так что ошибки будут выведены в консоль).
Мы определим отдельный модуль для каждой модели как уже обсуждалось выше. Начнём с создания каталога для моделей в корне проекта (/models), после чего создадим отдельные файлы для каждой модели:
/express-locallibrary-tutorial //the project root /models author.js book.js bookinstance.js genre.js
Скопируйте код схемы автора Author
(приведён ниже) в файл ./models/author.js . В схеме определено, что у автора есть обязательные поля имени и фамилии типа String
длиной не более 100 символов, и поля типа Date
дата рождения и дата смерти.
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var AuthorSchema = new Schema( { first_name: {type: String, required: true, max: 100}, family_name: {type: String, required: true, max: 100}, date_of_birth: {type: Date}, date_of_death: {type: Date}, } ); // Виртуальное свойство для полного имени автора AuthorSchema .virtual('name') .get(function () { return this.family_name + ', ' + this.first_name; }); // Виртуальное свойство - URL автора AuthorSchema .virtual('url') .get(function () { return '/catalog/author/' + this._id; }); //Export model module.exports = mongoose.model('Author', AuthorSchema);
Мы объявим также в схеме AuthorSchema виртуальное свойство "url" , которое позволит получить абсолютный URL конкретного экземпляра модели — используем это свойство в шаблонах, если потребуется получить связь с конкретным автором.
Заметка: Объявить в схеме URL как виртуальные свойства - хорошая идея, т.к. URL отдельного элемента при необходимости изменения требует коррекции только в одном месте.
Сейчас связь при помощи этого URL ещё не работает, так как у нас ещё нет кода, поддерживающего маршруты для экземпляров модели. Мы построим его в следующей статье!
В конце модуля экспортируется модель.
Скопируйте код схемы Book
(приведён ниже) в файл ./models/book.js. Большая часть кода подобна коду для модели автора — объявляется схема с рядом строковых полей, с виртуальным свойством URL для получения URL конкретных книг, затем модель экспортируется.
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var BookSchema = new Schema( { title: {type: String, required: true}, author: {type: Schema.ObjectId, ref: 'Author', required: true}, summary: {type: String, required: true}, isbn: {type: String, required: true}, genre: [{type: Schema.ObjectId, ref: 'Genre'}] } ); // Virtual for book's URL BookSchema .virtual('url') .get(function () { return '/catalog/book/' + this._id; }); //Export model module.exports = mongoose.model('Book', BookSchema);
Основное отличие в том, что созданы две ссылки на другие модели:
Author
, обязательный элемент.Genre
. Эта модель ещё не объявлена!Наконец, скопируйте код схемы BookInstance
(приведён ниже) в файл ./models/bookinstance.js. Схема BookInstance
представляет конкретный экземпляр книги, которую можно одолжить на время, и содержит информацию о доступности экземпляров книги, о дате возврата одолженной книги, о деталях версии или печатного экземпляра.
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var BookInstanceSchema = new Schema( { book: { type: Schema.ObjectId, ref: 'Book', required: true }, //ссылка на книгу imprint: {type: String, required: true}, status: {type: String, required: true, enum: ['Available', 'Maintenance', 'Loaned', 'Reserved'], default: 'Maintenance'}, due_back: {type: Date, default: Date.now} } ); // Virtual for bookinstance's URL BookInstanceSchema .virtual('url') .get(function () { return '/catalog/bookinstance/' + this._id; }); //Export model module.exports = mongoose.model('BookInstance', BookInstanceSchema);
В этой схеме новыми являются опции поля:
enum
: Позволяет указать допустимые значения строки. В нашем случае используются, чтобы задать статус доступности книги (применение enum (перечисления) означает, что мы ходим предотвратить ошибочное написание и произвольные значения статуса)default
: определяет значение статуса по умолчанию (maintenance) при создании экземпляра книги, и дату due_back
возврата книги (now,
сейчас). Отметьте, как используется функция Date при установке даты!Все остальное знакомо по предыдущим схемам.
Откройте файл ./models/genre.js и создайте схему для хранения жанра (категории книги, т.е. художественная или научная, романтика или военная история и т.д.).
Определение будет подобно другим моделям:
name
типа String
для указания жанра.url
для URL жанра.Вот так. У нас теперь есть все модели для создания сайта!
Для тестирования моделей (и для создания примеров книг и других элементов, которые потребуются в следующих статьях) выполним независимый скрипт, который создаст элементы каждого типа:
package.json
).
Заметка: Не обязательно понимать, как работает populatedb.js; он просто помещает некоторые данные в базу данных.
npm install async
app.js
):
node populatedb <your mongodb url>
Совет: Откройте свою базу на Lab. Вы сможете увидеть коллекции Book, Author, Genre, BookInstance (книги, авторы, жанры, экземпляры книг) и просмотреть содержащиеся в них документы.
В этой статье мы познакомились с БД и ОРМ (объектно-реляционными моделями) в системе Node/Express, узнали, как определяются схемы и модели Mongoose. Мы применили эти знания при проектировании и реализации моделей Book
, BookInstance
, Author
и Genre
для веб-сайта LocalLibrary.
В конце мы испытали свои модели путём создания ряда элементов (при помощи автономного скрипта). В следующей статье мы рассмотрим создание страниц, на которых будут показаны эти элементы.
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}