В прошлом уроке мы смотрели, как сервер выполняет запрос. Все двадцать уроков команды набирались в 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 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.