NeDB: легкая база данных JavaScript


Author:  Ande

Когда вы думаете о базе данных, первое, что может прийти вам в голову, - это MySQL, MongoDB или PostgreSQL. Хотя все это отличный выбор для хранения данных, для большинства приложений они перегружены.

Рассмотрим настольное приложение чата, написанное на платформе Electron на JavaScript. Хотя данные чата (сообщения, контакты, история и т. Д.), Скорее всего, будут исходить от сервера API, их также необходимо хранить локально в приложении. Потенциально у вас могут быть тысячи сообщений, и все они нужно будет хранить для быстрого доступа и поиска.

Ну так что ты делаешь? Один из вариантов - хранить все эти данные где-нибудь в файле и просто искать их каждый раз, когда вам нужно их получить, но это может быть неэффективно. Другой вариант - просто не кэшировать данные локально и выполнять вызов API-сервера каждый раз, когда вам нужно больше данных, но тогда ваше приложение будет менее отзывчивым и будет использовать гораздо больше сетевых данных.

Лучшая идея - использовать встроенную / облегченную базу данных, например NeDB . Это имеет больше смысла, потому что ваше приложение не будет обслуживать тысячи пользователей или обрабатывать гигабайты данных.

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

Облегченная база данных обычно хранит свои данные либо в памяти, либо в текстовом файле (с индексами для быстрого поиска). Это помогает уменьшить общую площадь, занимаемую базой данных в системе, что идеально подходит для небольших приложений. Для сравнения, размер tar-файла MySQL (для Mac OSX) составляет 337 МБ, а NeDB (несжатый, не минифицированный)

  • всего около 1,5 МБ.

Одна из величайших особенностей NeDB - это то, что его API является подмножеством API MongoDB, поэтому, если вы знакомы с MongoDB, у вас не должно возникнуть проблем с работой с NeDB после начальной настройки.

Примечание . Начиная с версии 1.8.0, NeDB еще не обновлял некоторые новые имена методов Mongo, такие как insertOne , insertMany и удаление findOne .

Начало работы с NeDB

Сначала установите модуль с NPM:

 $ npm install nedb --save 

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

Если вы планируете использовать его в браузере, используйте Bower для установки:

 $ bower install nedb 

Как и для всех клиентов базы данных, первым шагом является подключение к серверной базе данных. Однако в этом случае нет внешнего приложения для подключения, поэтому вместо этого нам просто нужно сообщить ему местоположение ваших данных. С NeDB у вас есть несколько вариантов сохранения ваших данных. Первый вариант - сохранить данные в памяти:

 var Datastore = require('nedb'); 
 var db = new Datastore(); 
 
 // Start issuing commands right away... 

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

Или другой вариант - сохранить данные в файл. Разница здесь в том, что вам нужно указать местоположение файла и загрузить данные.

 var Datastore = require('nedb'); 
 var db = new Datastore({ filename: 'path/to/your/file' }); 
 
 db.loadDatabase(function(err) { 
 // Start issuing commands after callback... 
 }); 

Если вы не хотите вызывать db.loadDatabase для каждой загружаемой базы данных, вы всегда можете использовать параметр autoload: true .

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

 var Datastore = require('nedb'); 
 var users = new Datastore({ filename: 'users.db', autoload: true }); 
 var tweets = new Datastore({ filename: 'tweets.db', autoload: true }); 
 var messages = new Datastore({ filename: 'messages.db', autoload: true }); 

Сохранение данных

После загрузки данных из файлов (или создания хранилища в памяти) вы захотите начать сохранять данные.

Как и в случае с драйверами Mongo, вы будете использовать insert для создания нового документа:

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 var scott = { 
 name: 'Scott', 
 twitter: '@ScottWRobinson' 
 }; 
 
 users.insert(scott, function(err, doc) { 
 console.log('Inserted', doc.name, 'with ID', doc._id); 
 }); 
 
 // Prints to console... 
 // (Note that ID will likely be different each time) 
 // 
 // "Inserted Scott with ID wt3Nb47axiOpme9u" 

Эту вставку можно легко расширить для одновременного сохранения нескольких документов. Используя тот же метод, просто передайте массив объектов, и каждый из них будет сохранен и возвращен вам в обратном вызове:

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 var people = []; 
 
 var scott = { 
 name: 'Scott Robinson', 
 age: 28, 
 twitter: '@ScottWRobinson' 
 }; 
 
 var elon = { 
 name: 'Elon Musk', 
 age: 44, 
 twitter: '@elonmusk' 
 }; 
 
 var jack = { 
 name: 'Jack Dorsey', 
 age: 39, 
 twitter: '@jack' 
 }; 
 
 people.push(scott, elon, jack); 
 
 users.insert(people, function(err, docs) { 
 docs.forEach(function(d) { 
 console.log('Saved user:', d.name); 
 }); 
 }); 
 
 // Prints to console... 
 // 
 // Saved user: Scott Robinson 
 // Saved user: Elon Musk 
 // Saved user: Jack Dorsey 

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

Загрузка данных

Теперь, когда у нас есть много сохраненных данных, пора извлечь их из базы данных. Опять же, мы будем следовать тому же соглашению, что и Mongo, с методом find

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 // Save a bunch of user data here... 
 
 users.findOne({ twitter: '@ScottWRobinson' }, function(err, doc) { 
 console.log('Found user:', doc.name); 
 }); 
 
 // Prints to console... 
 // 
 // Found user: Scott Robinson 

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

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 // Save a bunch of user data here... 
 
 users.find({ age: { $lt: 40 }}, function(err, docs) { 
 docs.forEach(function(d) { 
 console.log('Found user:', d.name); 
 }); 
 }); 
 
 // Prints to console... 
 // 
 // Found user: Jack Dorsey 
 // Found user: Scott Robinson 

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

  • $lt , $lte : меньше, меньше или равно
  • $gt , $gte : больше, больше или равно
  • $in : значение, содержащееся в массиве
  • $nin : значение не содержится в массиве
  • $ne : не равно
  • $exists : проверяет наличие (или отсутствие) данного свойства
  • $regex : сопоставить строку свойства с регулярным выражением

Вы также можете использовать стандартные операции сортировки, ограничения и пропуска. Если обратный вызов не передан методу find Cursor , который затем можно использовать для сортировки, ограничения и пропуска. Вот пример сортировки в алфавитном порядке по имени:

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 // Save a bunch of user data here... 
 
 users.find({}).sort({name: 1}).exec(function(err, docs) { 
 docs.forEach(function(d) { 
 console.log('Found user:', d.name); 
 }); 
 }); 
 
 // Prints to console... 
 // 
 // Found user: Elon Musk 
 // Found user: Jack Dorsey 
 // Found user: Scott Robinson 

Две другие операции, пропустить и ограничить, работают очень похоже на это.

find и findOne поддерживают еще несколько операторов, но мы не будем останавливаться на них здесь. Вы можете подробно прочитать об остальных этих операциях в разделе поиска документов README.

Удаление данных

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

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 // Save a bunch of user data here... 
 
 users.remove({ name: { $regex: /^Scott/ } }, function(err, numDeleted) { 
 console.log('Deleted', numDeleted, 'user(s)'); 
 }); 
 
 // Prints to console... 
 // 
 // Deleted 1 user(s) 

По умолчанию remove удаляет только один документ. Чтобы удалить несколько документов за один вызов, необходимо установить для параметра multi true .

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 // Save a bunch of user data here... 
 
 users.remove({}, { multi: true }, function(err, numDeleted) { 
 console.log('Deleted', numDeleted, 'user(s)'); 
 }); 
 
 // Prints to console... 
 // 
 // Deleted 3 user(s) 

Индексирование данных

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

В настоящее время поддерживаются три типа индексов:

  • unique : убедитесь, что данное поле уникально во всей коллекции
  • sparse : не индексировать документы, в которых данное поле не определено
  • expireAfterSeconds : удалить документ через заданное количество секунд ( время жизни или TTL)

На мой взгляд, индекс TTL особенно полезен, поскольку он избавляет вас от необходимости писать код для частого сканирования и удаления данных, срок действия которых истек.

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

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

 var Datastore = require('nedb'); 
 var users = new Datastore(); 
 
 users.ensureIndex({ fieldName: 'twitter', unique: true }); 
 
 var people = []; 
 
 var jack = { 
 name: 'Jack Dorsey', 
 age: 39, 
 twitter: '@jack' 
 }; 
 
 var jackSmith = { 
 name: 'Jack Smith', 
 age: 68, 
 twitter: '@jack' 
 }; 
 
 people.push(jack, jackSmith); 
 
 users.insert(people, function(err, docs) { 
 console.log('Uh oh...', err); 
 }); 
 
 // Prints to console... 
 // 
 // Uh oh... Can't insert key @jack, it violates the unique constraint 

Продолжая

Хотя API NeDB прост в использовании и все такое, с вашим кодом может стать довольно сложно работать, если он не будет хорошо продуман и организован. Именно здесь в игру вступают средства отображения объектных документов ( которые похожи на ORM ).

Используя Camo ODM (который я создал), вы можете просто рассматривать хранилища данных NeDB как классы JavaScript. Это позволяет вам указывать схему, проверять данные, расширять схемы и многое другое. Camo даже работает с MongoDB, поэтому вы можете использовать NeDB в средах тестирования / разработки, а затем использовать Mongo для своей производственной системы без необходимости изменять какой-либо код.

Вот быстрый пример подключения к базе данных, объявления объекта класса и сохранения некоторых данных:

 var connect = require('camo').connect; 
 var Document = require('camo').Document; 
 
 class User extends Document { 
 constructor() { 
 super(); 
 
 this.name = String; 
 this.age = Number; 
 this.twitter = Sring; 
 } 
 
 get firstName() { 
 return this.name.split(' ')[0]; 
 } 
 } 
 
 var scott = User.create({ 
 name: 'Scott Robinson', 
 age: 28, 
 twitter: '@ScottWRobinson' 
 }); 
 
 var elon = User.create({ 
 name: 'Elon Musk', 
 age: 44, 
 twitter: '@elonmusk' 
 }); 
 
 connect('nedb://memory').then(function(db) { 
 return Promise.all([scott.save(), elon.save()]); 
 }).then(function(users) { 
 users.forEach(function(u) { 
 console.log('Saved user:', u.firstName); 
 }); 
 
 return elon.delete(); 
 }).then(function() { 
 console.log('Deleted Elon!') 
 }); 
 
 // Prints to console... 
 // 
 // Saved user: Scott 
 // Saved user: Elon 
 // Deleted Elon! 

ODM - это гораздо больше, чем то, что я показал здесь. Для получения дополнительной информации ознакомьтесь с этой статьей или документацией в README проекта.

Заключение

Поскольку NeDB довольно маленький (и довольно быстрый!), Его очень легко добавить практически в любой проект. А с Camo в смеси вам понадобится всего несколько строк кода для объявления объектов на основе классов, которые намного проще создавать, удалять и манипулировать.

Если вы когда-либо использовали NeDB в одном из своих проектов, мы будем рады услышать об этом. Дайте нам знать об этом в комментариях!


Смотрите также