Menu
CoddyTech

Operadores de Atualização

Parte da seção Bancos de dados com MongoDB do Journey de Node.js da Coddy. Lição 15 de 29.

Adicionar um exemplar ou uma etiqueta deve alterar um livro diretamente, sem lê-lo, modificá-lo em JavaScript e gravá-lo de volta.

$inc adiciona um número a um campo, começando em 0 quando o campo não existe. $push acrescenta um valor a um campo de array, criando o array quando ele não existe. Uma atualização pode conter vários operadores:

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

O banco de dados aplica a alteração por conta própria, então duas solicitações feitas ao mesmo tempo contam. $set não serve aqui: ele substitui todo o array badges pelo valor que você fornece.

challenge icon

Desafio

Fácil

A estante conta quantas cópias de cada livro você tem, e os leitores podem adicionar tags aos livros. Ambas as alterações acontecem no banco de dados, em uma única atualização cada, sem ler o livro antes.

  1. Em server.js, adicione POST /books/:id/copies. Não precisa de corpo. Ele adiciona 1 ao campo copies do livro. Um livro que ainda não tem copies fica com "copies": 1.
  2. Adicione POST /books/:id/tags. O corpo informa uma tag, por exemplo { "tag": "classic" }. A rota adiciona a tag ao final da lista tags do livro e mantém as tags que já estavam lá. Um livro que ainda não tem tags fica com "tags": ["classic"].
  3. Ambas as rotas usam parseId para um id inválido, que resulta em 400. Ambas respondem com 404 quando nenhum livro tem esse _id. Caso contrário, ambas respondem com o status 200 e o livro atualizado completo. Depois de adicionar duas cópias e as tags classic e depois favorite, Dune fica assim:
    { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi", "copies": 2, "tags": ["classic", "favorite"] }
    Experimente: Envie GET /books e copie o _id de Dune. Envie POST /books/<Dune's _id>/copies com o corpo vazio: você recebe Dune com "copies": 1. Envie novamente: "copies": 2. Envie POST /books/<Dune's _id>/tags com { "tag": "classic" }: "tags": ["classic"]. Envie novamente com { "tag": "favorite" }: "tags": ["classic", "favorite"]. Envie POST /books/000000000000000000000000/copies: você recebe 404.

Experimente você mesmo

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

// Todo erro retorna { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// Retorna :id como um ObjectId ou null após responder com 400 para um id inválido.
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');
});

Todas as lições de Bancos de dados com MongoDB

Pratique por conta própria: Playground de Express