Menu
CoddyTech

عدّ المستندات

جزء من قسم قواعد البيانات باستخدام MongoDB في رحلة Node.js على Coddy. الدرس 19 من 29.

تحتاج لوحة المعلومات إلى أرقام، وتحميل كل مستند لمجرد قراءة طول المصفوفة يجعل قاعدة البيانات ترسلها كلها.

تجيب countDocuments(filter) عن عدد المستندات المطابقة، من دون إرسال أيٍّ منها. إذا لم تحدد عامل تصفية، فستعدّ المجموعة بأكملها.

const open = await tickets.countDocuments({ status: 'open' });
const all = await tickets.countDocuments();

أنشئ عامل التصفية من الاستعلام بالطريقة التي يفعلها GET /books، ومرّر عامل التصفية نفسه إلى دالة العد. فالعدّ الذي يتجاهل عامل التصفية يعطي العدد الصحيح للسؤال الخطأ: جميع التذاكر، بينما طلب العميل التذاكر المفتوحة.

challenge icon

التحدي

سهل

تعرض لوحة المعلومات عدد الكتب والتقييمات. أجرِ إحصاءها في قاعدة البيانات بدلًا من تحميل كل مستند.

  1. في server.js، أضف مسار GET /stats أسفل التعليق // Add GET /stats here..
  2. يستجيب برمز الحالة 200 وعدد المستندات في كل مجموعة. مع كتب البداية وعدم وجود تقييمات، تكون النتيجة:
    { "books": 3, "ratings": 0 }
  3. يجعل ?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');
});
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس قواعد البيانات باستخدام MongoDB

تدرّب بنفسك: Playground لـ Express