عدّ المستندات
جزء من قسم قواعد البيانات باستخدام MongoDB في رحلة Node.js على Coddy. الدرس 19 من 29.
تحتاج لوحة المعلومات إلى أرقام، وتحميل كل مستند لمجرد قراءة طول المصفوفة يجعل قاعدة البيانات ترسلها كلها.
تجيب countDocuments(filter) عن عدد المستندات المطابقة، من دون إرسال أيٍّ منها. إذا لم تحدد عامل تصفية، فستعدّ المجموعة بأكملها.
const open = await tickets.countDocuments({ status: 'open' });
const all = await tickets.countDocuments();أنشئ عامل التصفية من الاستعلام بالطريقة التي يفعلها GET /books، ومرّر عامل التصفية نفسه إلى دالة العد. فالعدّ الذي يتجاهل عامل التصفية يعطي العدد الصحيح للسؤال الخطأ: جميع التذاكر، بينما طلب العميل التذاكر المفتوحة.
التحدي
سهلتعرض لوحة المعلومات عدد الكتب والتقييمات. أجرِ إحصاءها في قاعدة البيانات بدلًا من تحميل كل مستند.
- في
server.js، أضف مسارGET /statsأسفل التعليق// Add GET /stats here.. - يستجيب برمز الحالة 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
تدرّب بنفسك: Playground لـ Express