Menu
CoddyTech

Đếm tài liệu

Thuộc phần Cơ sở dữ liệu với MongoDB trong hành trình Node.js của Coddy. Bài học 19 trên 29.

Bảng điều khiển cần các con số, nhưng nếu tải mọi tài liệu chỉ để đọc độ dài của mảng thì cơ sở dữ liệu sẽ gửi tất cả tài liệu đó.

countDocuments(filter) cho biết có bao nhiêu tài liệu khớp mà không gửi tài liệu nào. Nếu không có bộ lọc, hàm này đếm toàn bộ bộ sưu tập.

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

Tạo bộ lọc từ truy vấn giống như cách GET /books thực hiện, rồi truyền chính bộ lọc đó cho hàm đếm. Nếu hàm đếm bỏ qua bộ lọc, kết quả sẽ đúng với câu hỏi sai: đếm mọi vé trong khi ứng dụng yêu cầu đếm các vé đang mở.

challenge icon

Thử thách

Dễ

Bảng điều khiển hiển thị số lượng sách và lượt đánh giá. Hãy đếm chúng trong cơ sở dữ liệu thay vì tải mọi tài liệu.

  1. Trong server.js, bên dưới chú thích // Add GET /stats here., hãy thêm một tuyến GET /stats.
  2. Tuyến này phản hồi với mã trạng thái 200 và số lượng tài liệu trong mỗi bộ sưu tập. Với sách có sẵn ban đầu và chưa có lượt đánh giá, kết quả là:
    { "books": 3, "ratings": 0 }
  3. ?genre=scifi khiến books chỉ đếm những cuốn sách thuộc thể loại đó. ratings luôn đếm mọi lượt đánh giá, bất kể thể loại nào. Thử nhé: Gửi GET /stats: bạn nhận được { "books": 3, "ratings": 0 }. Gửi GET /stats?genre=scifi: { "books": 2, "ratings": 0 }. Gửi GET /stats?genre=poetry: { "books": 0, "ratings": 0 }. Sao chép _id của Dune từ GET /books, rồi gửi PUT /books/<Dune's _id>/ratings với { "user": "ada", "stars": 4 }. Gửi lại GET /stats: { "books": 3, "ratings": 1 }.

Tự làm thử

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());

// Mọi lỗi đều trả về { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// Trả về :id dưới dạng ObjectId, hoặc null sau khi trả về 400 nếu id không hợp lệ.
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());
});

// Thêm GET /stats vào đây.

app.listen(3000, () => {
    console.log('Bookshelf is running on port 3000');
});
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Cơ sở dữ liệu với MongoDB

Tự luyện tập: Playground Express