Чтобы работать с MongoDB из Node.js, установите официальный драйвер (npm install mongodb), подключитесь через new MongoClient(uri), выберите базу через client.db('name') и коллекцию через db.collection('name'). Каждая операция (insertOne, find, updateOne, deleteOne) возвращает промис, поэтому её нужно await. Документы записываются и читаются как обычные объекты JavaScript.
Программа выводит [ 'Dune', 'Neuromancer', 'Hyperion' ]. Базы shop и коллекции books раньше не было: MongoDB создаёт их при первой вставке, а toArray() загружает все документы, на которые указывает курсор find().
Поиск документов с фильтрами и projection
Фильтр это объект, который описывает нужные документы. Обычные значения сравниваются на точное совпадение; операторы вроде $gt, $lt, $in и $regex пишутся во вложенном объекте. projection во втором аргументе выбирает, какие поля вернутся.
В recent две самые новые книги после 1960 года, сначала Hyperion, потом Neuromancer, только с title и year; findOne возвращает первое совпадение (или null), а countDocuments насчитывает 2 книги за 10 и дороже. Замените $gt: 1960 на $gt: 1985, и останется только Hyperion.
Обновление и удаление
updateOne(filter, update) меняет первый подходящий документ. В обновлении используются операторы: $set заменяет поля, $inc прибавляет к числу, $unset удаляет поле, а $push добавляет в массив. updateMany и deleteMany работают со всеми совпадениями.
Обновление нашло и изменило один документ, так что Dune теперь стоит 10, а на складе 2, и после удаления книги без остатка остаётся 1. В обновлении обязательны операторы: передайте { price: 10 } без $set, и драйвер откажет (заменяет документ целиком вызов replaceOne).
ObjectId и индексы
Каждый документ получает _id; если вы его не задали, драйвер создаёт ObjectId. Чтобы найти документ по id, который пришёл строкой (например, из URL), оберните его в new ObjectId(id). Индекс делает поиск по полю быстрым, а уникальный индекс заставляет MongoDB отклонять дубликаты.
ObjectId в виде строки это 24 шестнадцатеричных символа, а поиск по обычной строке ничего не находит, поэтому та строка выводит null. Вторая вставка нарушает уникальный индекс и падает с кодом 11000, так и определяют ситуацию "email уже занят".
MongoDB в API на Express
Подключитесь один раз при запуске сервера и используйте коллекцию везде; не открывайте новое подключение на каждый запрос. Драйвер держит пул соединений и переиспользует их.
GET /books возвращает обе книги, у каждой есть _id. Скопируйте один id в GET /books/<id>, чтобы получить одну книгу, или отправьте POST /books с JSON телом вроде {"title": "Foundation", "year": 1951} и снова запросите список.
В настоящем приложении строка подключения читается из process.env.MONGODB_URI, а не пишется в коде.
Если нужны схемы и валидация поверх драйвера, их даёт Mongoose (npm install mongoose):
const Book = mongoose.model('Book', new mongoose.Schema({ title: String, year: Number }));
await Book.create({ title: 'Dune', year: 1965 });
Частые ошибки
Забыли toArray(). find() возвращает курсор, а не документы. В логе будет объект курсора; вызовите toArray() или пройдите по нему через for await.
Подключение на каждый запрос. Каждый new MongoClient открывает свой пул. Создайте один клиент при запуске и используйте его везде.
Клиент не закрыт в скрипте. Открытый клиент не даёт процессу завершиться, когда ваш код уже отработал. Вызывайте await client.close() в конце скриптов; сервер держит его открытым всё время работы.
Часто задаваемые вопросы
Что выбрать: драйвер mongodb или Mongoose?
Драйвер это официальный и более тонкий слой: вы работаете с обычными объектами и собственным языком запросов MongoDB. Mongoose добавляет поверх него схемы, валидацию и модели, это удобно, когда нужно проверять каждый документ. Оба работают с одной и той же базой, а Mongoose внутри использует драйвер.
Нужно ли сначала создавать базу данных или коллекцию?
Нет. MongoDB создаёт и то, и другое при первой вставке документа. client.db('shop') и db.collection('books') только возвращают ссылки на них.
Где хранить строку подключения?
В переменной окружения, например MONGODB_URI, и читать её через process.env.MONGODB_URI. В строке облачной базы есть пароль, поэтому её нельзя коммитить в репозиторий.
Почему find ничего не находит, когда я ищу по id?
_id хранится как ObjectId, а не как строка, поэтому { _id: '65f1...' } ничего не находит. Сначала преобразуйте его: { _id: new ObjectId(id) }.