Як користуватися MongoDB + Mongoose з Node.js - найкращі практики для розробників, що працюють на задній панелі

MongoDB, безсумнівно, є одним із найпопулярніших варіантів вибору баз даних NoSQL сьогодні. І в ньому чудова спільнота та екосистема.

У цій статті ми розглянемо деякі найкращі практики, яких слід дотримуватися, коли ви налаштовуєте MongoDB та Mongoose за допомогою Node.js.

Передумови для цієї статті

Ця стаття є однією із частин навчального шляху кодового коду, де ми починаємо з базових основ і детально висвітлюємо їх. Тому я припускаю, що ви вже маєте певний досвід роботи з JavaScript (і Node.js).

Зараз ми тут:

Якщо у вас дуже мало досвіду роботи з Node.js / JavaScript або загальним інтерфейсом, це, мабуть, гарне місце для початку. Ви також можете знайти безкоштовний курс на Mongoose + MongoDB + Node.js тут. Зануримось.

Навіщо вам мангуст?

Щоб зрозуміти, навіщо нам Mongoose, давайте зрозуміємо, як MongoDB (і база даних) працює на рівні архітектури.

  • У вас є сервер баз даних (наприклад, сервер спільноти MongoDB)
  • У вас запущений сценарій Node.js (як процес)

Сервер MongoDB прослуховує TCP-сокет (зазвичай), і ваш процес Node.js може підключатися до нього за допомогою TCP-з'єднання.

Але на вершині TCP, MongoDB також має власний протокол для розуміння того, що саме клієнт (наш процес Node.js) хоче робити з базою даних.

Для цього спілкування, замість того, щоб вивчати повідомлення, які ми повинні відправляти на рівні TCP, ми абстрагуємо це за допомогою програмного забезпечення "драйвер", що в цьому випадку називається драйвером MongoDB. Драйвер MongoDB доступний тут як пакет npm.

Тепер пам’ятайте, драйвер MongoDB відповідає за підключення та абстрагування від вас запиту / відповідей на комунікації низького рівня, але це лише заводить вас як розробника.

Оскільки MongoDB - це безсхемна база даних, вона надає вам більше потужності, ніж вам потрібно для початківців. Більша потужність означає більшу площу поверхні, щоб помилитися. Вам потрібно зменшити площу вашої помилки та викручування, яку ви можете внести у свій код. Вам потрібно щось більше.

Зустріньте Мангуста. Mongoose - це абстракція від власного драйвера MongoDB (пакет npm, про який я згадав вище).

Загальне емпіричне правило для абстракцій (наскільки я розумію) полягає в тому, що з кожною абстракцією ви втрачаєте деяку робочу силу низького рівня. Але це не обов'язково означає, що це погано. Іноді це підвищує продуктивність 1000 разів +, тому що вам і так ніколи не потрібно мати повний доступ до базового API.

Хорошим способом подумати про це є те, що ви технічно створюєте програму чату в режимі реального часу як на C, так і на Python.

Приклад Python було б набагато простіше і швидше для вас, як розробника, реалізувати з більшою продуктивністю.

C може бути більш ефективним, але це буде мати величезні витрати на продуктивність / швидкість розробки / помилки / збої. Крім того, здебільшого вам не потрібно мати потужність C, яка дає вам можливість реалізувати веб-сокети.

Подібним чином, за допомогою Mongoose ви можете обмежити свою поверхню нижчим рівнем доступу до API, але розблокувати багато потенційних прибутків і хороший DX.

Як підключити Mongoose + MongoDB

По-перше, давайте швидко подивимося, як вам слід підключитися до вашої бази даних MongoDB у 2020 році за допомогою Mongoose:

mongoose.connect(DB_CONNECTION_STRING, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false })

Цей формат підключення гарантує, що ви використовуєте новий синтаксичний аналізатор URL від Mongoose і не використовуєте жодних застарілих методів. Якщо хочете, ви можете детально прочитати всі ці повідомлення про припинення роботи.

Як виконувати операції мангуста

Тепер давайте швидко обговоримо операції з Mongoose та те, як вам їх виконувати.

Mongoose пропонує два варіанти:

  1. Запит на основі курсору
  2. Повний запит отримання

Запит на основі курсору

Запит на основі курсору означає, що ви працюєте з одним записом одночасно, коли ви отримуєте один або пакет документів одночасно з бази даних. Це ефективний спосіб роботи з величезними обсягами даних в обмеженому середовищі пам'яті.

Уявіть, що вам потрібно проаналізувати документи загальним розміром 10 ГБ на хмарному сервері 1 ГБ / 1 бал. Ви не можете отримати всю колекцію, оскільки вона не вміщується у вашій системі. Курсор - тут хороший (і єдиний?) Варіант.

Повне отримання запитів

Це тип запиту, де ви отримуєте повну відповідь на ваш запит відразу. Здебільшого цим ви будете користуватися. Тому ми зосередимося в основному на цьому методі тут.

Як користуватися моделями Mongoose

Моделі - наддержава Мангуста. Вони допомагають вам застосовувати правила "схеми" та забезпечують безперебійну інтеграцію коду Node у виклики бази даних.

Найпершим кроком є ​​визначення хорошої моделі:

import mongoose from 'mongoose' const CompletedSchema = new mongoose.Schema( { type: { type: String, enum: ['course', 'classroom'], required: true }, parentslug: { type: String, required: true }, slug: { type: String, required: true }, userid: { type: String, required: true } }, { collection: 'completed' } ) CompletedSchema.index({ slug: 1, userid: 1 }, { unique: true }) const model = mongoose.model('Completed', CompletedSchema) export default model 

Це один обрізаний приклад безпосередньо з кодової бази codedamn. Кілька цікавих речей, на які слід звернути увагу тут:

  1. Намагайтеся дотримуватися required: trueвсіх полів, які є обов’язковими. Це може врятувати для вас величезну біль, якщо ви не використовуєте статичну систему перевірки типу, як TypeScript, щоб допомогти вам з правильними назвами властивостей під час створення об’єкта. Плюс безкоштовна перевірка - це теж круто.
  2. Визначте індекси та унікальні поля. uniqueвластивість також можна додати в схему. Покажчики - це широка тема, тому я не буду тут заглиблюватися. Але у великому масштабі вони дійсно можуть допомогти вам значно пришвидшити ваші запити.
  3. Define a collection name explicitly. Although Mongoose can automatically give a collection name based on the name of model (Completed here, for example), this is way too much abstraction in my opinion. You should at least know about your database names and collections in your codebase.
  4. Restrict values if you can, using enums.

How to perform CRUD Operations

CRUD means Create, Read, Update and Delete. These are the four fundamental options with which you can perform any sort of data manipulation in a database. Let's quickly see some examples of these operations.

The Create Operation

This simply means creating a new record in a database. Let's use the model we defined above to create a record:

try { const res = await CompletedSchema.create(record) } catch(error) { console.error(error) // handle the error }

Again, a few pointers here:

  1. Use async-await instead of callbacks (nice on the eyes, no ground breaking performance benefit as such)
  2. Use try-catch blocks around queries because your query can fail for a number of reasons (duplicate record, incorrect value, and so on)

The Read Operation

This means reading existing values from the database. it's simple just like it sounds, but there are a couple of gotchas you should know with Mongoose:

const res = await CompletedSchema.find(info).lean()
  1. Can you see the lean() function call there? It is super useful for performance. By default, Mongoose processes the returned document(s) from the database and adds its magical methods on it (for example .save)
  2. When you use .lean(), Mongoose returns plain JSON objects instead of memory and resource heavy documents. Makes queries faster and less expensive on your CPU, too.
  3. However, you can omit .lean() if you are actually thinking of updating data (we'll see that next)

The Update Operation

If you already have a Mongoose document with you (without firing with .lean()), you can simply go ahead and modify the object property, and save it using object.save():

const doc = await CompletedSchema.findOne(info) doc.slug = 'something-else' await doc.save()

Remember that here, there are two database calls made. The first one is on findOne and the second one is on doc.save.

If you can, you should always reduce the number of requests hitting the database (because if you're comparing memory, network, and disk, network is almost always the slowest).

In the other case, you can use a query like this:

const res = await CompletedSchema.updateOne(, ).lean()

and it will only make a single call to the database.

The Delete Operation

Delete is also straightforward with Mongoose. Let's see how you can delete a single document:

const res = await CompletedSchema.deleteOne()

Just like updateOne, deleteOne also accepts the first argument as the matching condition for the document.

There is also another method called deleteMany which should be used only when you know you want to delete multiple documents.

In any other case, always use deleteOne to avoid accidental multiple deletes, especially when you're trying to execute queries yourself.

Conclusion

This article was a simple introduction to the Mongoose and MongoDB world for Node.js developers.

If you enjoyed this article, you can step up your game even more as a developer by following the codedamn backend learning path. Please feel free to reach out to me on Twitter for any feedback!