Menu
CoddyTech

Toán tử cập nhật

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 15 trên 29.

Thêm một bản sao hoặc một thẻ nên cập nhật trực tiếp cuốn sách, không cần đọc, thay đổi trong JavaScript rồi ghi lại.

$inc cộng một số vào một trường, bắt đầu từ 0 nếu trường đó chưa tồn tại. $push thêm một giá trị vào cuối trường mảng, tạo mảng nếu mảng chưa tồn tại. Một lần cập nhật có thể chứa nhiều toán tử:

await players.updateOne(
    { name: 'Ada' },
    { $inc: { wins: 1 }, $push: { badges: 'streak' } }
);

Cơ sở dữ liệu tự áp dụng thay đổi, vì vậy hai yêu cầu xảy ra cùng lúc đều được tính. $set không phù hợp ở đây: nó thay thế toàn bộ mảng badges bằng giá trị bạn cung cấp.

challenge icon

Thử thách

Dễ

Giá sách đếm số bản của mỗi cuốn sách bạn sở hữu, và độc giả có thể gắn thẻ cho sách. Cả hai thay đổi đều diễn ra bên trong cơ sở dữ liệu (database), mỗi thay đổi chỉ cần một lần cập nhật, mà không cần đọc sách trước.

  1. Trong server.js, thêm POST /books/:id/copies. Tuyến này không cần body. Tuyến cộng thêm 1 vào trường copies của sách. Sách chưa có copies sẽ có giá trị "copies": 1.
  2. Thêm POST /books/:id/tags. Body chứa tên của một thẻ, chẳng hạn { "tag": "classic" }. Tuyến thêm thẻ vào cuối danh sách tags của sách và giữ nguyên các thẻ đã có. Sách chưa có tags sẽ có giá trị "tags": ["classic"].
  3. Cả hai tuyến đều dùng parseId khi id không hợp lệ, khi đó trả về 400. Cả hai trả về 404 nếu không có sách nào mang _id đó. Nếu không, cả hai trả về trạng thái 200 cùng toàn bộ thông tin sách đã cập nhật. Sau khi thêm hai bản và các thẻ classic rồi favorite, Dune trông như sau:
    { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi", "copies": 2, "tags": ["classic", "favorite"] }
    Thử ngay: Gửi GET /books và sao chép _id của Dune. Gửi POST /books/<Dune's _id>/copies với body rỗng: bạn nhận được Dune với "copies": 1. Gửi lại: "copies": 2. Gửi POST /books/<Dune's _id>/tags với { "tag": "classic" }: "tags": ["classic"]. Gửi lại với { "tag": "favorite" }: "tags": ["classic", "favorite"]. Gửi POST /books/000000000000000000000000/copies: bạn nhận được 404.

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

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ề mã 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;
    await books.insertOne(book);
    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.listen(3000, () => {
    console.log('Bookshelf is running on port 3000');
});

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

Tự luyện tập: Playground Express