Подсчёт документов
Часть раздела Базы данных с MongoDB путешествия по Node.js на Coddy. Урок 19 из 29.
Для панели нужны числа, а загрузка каждого документа только ради чтения длины массива заставляет базу данных отправлять их все.
countDocuments(filter) сообщает, сколько документов соответствует условию, не отправляя ни одного из них. Если фильтр не указан, подсчитываются все документы коллекции.
const open = await tickets.countDocuments({ status: 'open' });
const all = await tickets.countDocuments();Составь фильтр на основе запроса, как это делает GET /books, и передай тот же фильтр в подсчёт. Подсчёт без учёта фильтра даст правильное число, но ответит не на тот вопрос: посчитает все заявки, хотя клиент запросил открытые.
Задание
ЛегкоНа панели отображается количество книг и оценок. Подсчитай их в базе данных, не загружая каждый документ.
- В
server.js, ниже комментария// Add GET /stats here., добавь маршрутGET /stats. - Он отвечает со статусом 200 и количеством документов в каждой коллекции. Для стартовых книг и отсутствия оценок это будет:
{ "books": 3, "ratings": 0 } ?genre=scifiзаставляетbooksподсчитывать только книги этого жанра.ratingsвсегда подсчитывает все оценки, независимо от жанра. Попробуй: ОтправьGET /stats: получишь{ "books": 3, "ratings": 0 }. ОтправьGET /stats?genre=scifi:{ "books": 2, "ratings": 0 }. ОтправьGET /stats?genre=poetry:{ "books": 0, "ratings": 0 }. Скопируй_idкниги Dune изGET /books, затем отправьPUT /books/<Dune's _id>/ratingsс{ "user": "ada", "stars": 4 }. ОтправьGET /statsещё раз:{ "books": 3, "ratings": 1 }.
Попробуйте сами
import express from 'express';
import { MongoClient, ObjectId } from 'mongodb';
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('shelf');
const books = db.collection('books');
const ratings = db.collection('ratings');
await books.createIndex({ isbn: 1 }, { unique: true });
await books.insertMany([
{ title: 'Dune', author: 'Frank Herbert', year: 1965, isbn: '9780441013593', genre: 'scifi' },
{ title: 'Kindred', author: 'Octavia E. Butler', year: 1979, isbn: '9780807083697', genre: 'scifi' },
{ title: 'Emma', author: 'Jane Austen', year: 1815, isbn: '9780141439587', genre: 'classic' },
]);
const app = express();
app.use(express.json());
// При любой ошибке возвращается { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// Возвращает :id как ObjectId или null после ответа с кодом 400, если id некорректен.
function parseId(req, res) {
if (ObjectId.isValid(req.params.id)) {
return new ObjectId(req.params.id);
}
sendError(res, 400, 'Not a valid id');
return null;
}
app.get('/', (req, res) => {
res.send('Bookshelf API');
});
app.get('/books', async (req, res) => {
const filter = {};
if (req.query.author) {
filter.author = req.query.author;
}
if (req.query.from || req.query.to) {
filter.year = {};
if (req.query.from) {
filter.year.$gte = Number(req.query.from);
}
if (req.query.to) {
filter.year.$lte = Number(req.query.to);
}
}
const list = await books.find(filter).toArray();
res.json(list);
});
app.post('/books', async (req, res) => {
const book = req.body;
try {
await books.insertOne(book);
} catch (err) {
if (err.code === 11000) {
return sendError(res, 409, 'That ISBN is already on the shelf');
}
throw err;
}
res.status(201).json(book);
});
app.get('/books/newest', async (req, res) => {
const n = Number(req.query.n) || 3;
const list = await books
.find({}, { projection: { title: 1, year: 1, _id: 0 } })
.sort({ year: -1 })
.limit(n)
.toArray();
res.json(list);
});
app.get('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const book = await books.findOne({ _id: id });
if (!book) {
return sendError(res, 404, 'No such book');
}
res.json(book);
});
app.patch('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $set: req.body });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.post('/books/:id/copies', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $inc: { copies: 1 } });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.post('/books/:id/tags', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $push: { tags: req.body.tag } });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.delete('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.deleteOne({ _id: id });
if (result.deletedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.status(204).end();
});
app.put('/books/:id/ratings', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const { user, stars } = req.body;
await ratings.updateOne({ bookId: id, user }, { $set: { stars } }, { upsert: true });
res.json({ user, stars });
});
app.get('/books/:id/ratings', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
res.json(await ratings.find({ bookId: id }).toArray());
});
// Добавьте здесь GET /stats.
app.listen(3000, () => {
console.log('Bookshelf is running on port 3000');
});
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Базы данных с MongoDB
Потренируйтесь самостоятельно: Песочница Express