Раздел 36 · MongoDB с нуля

MongoDB из приложения: драйвер Node.js

junior~25 мин

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

В прошлом уроке мы смотрели, как сервер выполняет запрос. Все двадцать уроков команды набирались в mongosh руками. Но книжный магазин не живёт в консоли: каталог показывает сайт, заказы оформляет бэкенд. Сегодня переносим запросы в программу на Node.js и смотрим, что меняется, когда с базой говорит код.

Переводчик на переговорах

На переговорах с иностранной компанией ты говоришь по-русски, а переводчик передаёт сказанное партнёрам на их языке и переводит ответы обратно. Тебе не нужно знать, как устроена чужая грамматика.

Сервер MongoDB понимает свой сетевой протокол и формат BSON, а приложение пишет на JavaScript. Между ними стоит драйвер, тот самый переводчик. Ты передаёшь ему обычные объекты JavaScript, он упаковывает их в BSON, отправляет на сервер и возвращает ответ уже объектами.

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

Установка и подключение

Официальный драйвер ставится из npm. Примеры урока проверены на версии 7.7:

npm install mongodb

Подключение начинается с MongoClient. Ему нужна строка подключения из урока про установку:

import { MongoClient } from 'mongodb';

const client = new MongoClient('mongodb://127.0.0.1:27017');

const db = client.db('test');
const books = db.collection('books');

console.log(await books.countDocuments());

await client.close();
9

client.db('test') это то же, что use test в mongosh, а db.collection('books') то же, что db.books. Явно вызывать client.connect() не обязательно: драйвер подключится сам при первом запросе.

Главное отличие от mongosh: все методы асинхронные. Запрос уходит по сети, и ответ приходит не сразу, поэтому перед каждым вызовом стоит await.

Один клиент на всё приложение

MongoClient держит не одно соединение, а пул соединений. Открыть соединение дорого: сетевое рукопожатие, проверка пароля, иногда TLS. Пул держит несколько соединений открытыми и выдаёт их запросам по очереди, как стойка проката выдаёт велосипеды.

Отсюда правило: создай один MongoClient при старте приложения и используй его везде. Частая ошибка новичка: создавать клиента внутри обработчика запроса. Тогда каждый посетитель сайта открывает новый пул, и сервер быстро упирается в предел соединений.

Размер пула задаётся опцией, по умолчанию он до 100 соединений:

const client = new MongoClient('mongodb://127.0.0.1:27017', { maxPoolSize: 10 });

Закрывают клиента один раз, при остановке приложения, через await client.close(). Скрипт, который забыл это сделать, не завершится: открытые соединения держат процесс живым.

Чтение: findOne и курсор

findOne возвращает документ или null, как в mongosh. Проекцию драйвер принимает в объекте настроек:

const one = await books.findOne({ _id: 7 }, { projection: { title: 1, price: 1 } });
console.log(one);
{ _id: 7, title: 'Грокаем алгоритмы', price: 1290 }

С find всё интереснее. mongosh сам печатает первые двадцать документов, а в коде find возвращает курсор: указатель на результат, который приходит с сервера партиями. Курсор читают двумя способами.

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

const cursor = books
  .find({ genre: 'фантастика' })
  .project({ _id: 0, title: 1, price: 1 })
  .sort({ price: 1, _id: 1 });

for await (const book of cursor) {
  console.log(`${book.title}: ${book.price} ₽`);
}
Пикник на обочине: 450 ₽
Трудно быть богом: 520 ₽

Целиком через toArray(). Удобно для маленьких выборок, но на миллионе документов массив съест память:

const cheap = await books
  .find({ price: { $lt: 400 } })
  .project({ _id: 0, title: 1 })
  .sort({ _id: 1 })
  .toArray();
console.log(cheap);
[ { title: 'Собачье сердце' }, { title: 'Шинель' } ]

Методы sort, skip, limit и project работают как в уроке про пагинацию.

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.