Menu
CoddyTech

Updating Documents

Part of the Databases with MongoDB section of Coddy's Node.js journey. Lesson 14 of 29.

Correcting a book's year should change that one field and leave the others alone.

updateOne(filter, update) changes the first document the filter matches. The update is made of operators: $set writes the fields you give it and keeps every other field. A plain object with no operator is refused with "Update document requires atomic operators".

const result = await players.updateOne(
    { name: 'Ada' },
    { $set: { score: 120, level: 4 } }
);
result.matchedCount;  // 1 if a player matched, 0 if none
result.modifiedCount; // 0 when the values were already the same

Tell a missing document from an unchanged one with matchedCount, not modifiedCount. The update does not answer the document, so read it again with findOne.

challenge icon

Challenge

Easy

A client wants to fix one field of a book, such as a wrong year, without sending the whole book again.

  1. In server.js, below the comment // Add PATCH /books/:id here., add a PATCH /books/:id route.
  2. Start it the way GET /books/:id now starts: call parseId(req, res). When the id is bad, parseId has already responded with 400 and returns null, so stop there.
  3. Change only the fields the body sends, and leave every other field as it was. A body of { "year": 1966 } changes the year and nothing else.
  4. When no book has that _id, respond with status 404 and an error, for example { "error": "No such book" }.
  5. Otherwise, respond with status 200 and the whole book as it is now stored:
    { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1966, "isbn": "9780441013593", "genre": "scifi" }
    Try it: Send GET /books and copy Dune's _id. Copy it again after every Run, because each Run gives new ids. Send PATCH /books/ plus Dune's _id, with the body { "year": 1966 }. You get 200 and Dune with "year": 1966, with every other field unchanged, and the Data tab shows the same. Send the same request again: you still get 200, because the book exists even though nothing changed this time. Send PATCH /books/000000000000000000000000 with { "year": 1966 }: you get 404. Send PATCH /books/42: you get 400.

Try it yourself

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

// Every error answers { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// The :id as an ObjectId, or null after answering 400 for a bad 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;
    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);
});

// Add PATCH /books/:id here.

app.listen(3000, () => {
    console.log('Bookshelf is running on port 3000');
});
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Databases with MongoDB

Practice on your own: Express playground