Menu
CoddyTech

Opérateurs de mise à jour

Fait partie de la section Bases de données avec MongoDB du Journey Node.js de Coddy. Leçon 15 sur 29.

Ajouter une copie ou une étiquette doit modifier un livre directement, sans le lire, le modifier en JavaScript puis le réécrire.

$inc ajoute un nombre à un champ, en partant de 0 lorsque le champ est absent. $push ajoute une valeur à la fin d’un champ tableau, en créant le tableau lorsqu’il est absent. Une mise à jour peut contenir plusieurs opérateurs :

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

La base de données applique elle-même la modification, de sorte que deux requêtes effectuées au même moment sont toutes deux prises en compte. $set ne convient pas ici : il remplace tout le tableau badges par la valeur que vous lui donnez.

challenge icon

Défi

Facile

La bibliothèque compte le nombre d’exemplaires de chaque livre que tu possèdes, et les lecteurs peuvent ajouter des étiquettes aux livres. Ces deux modifications s’effectuent dans la base de données, chacune en une seule mise à jour, sans lire le livre au préalable.

  1. Dans server.js, ajoute POST /books/:id/copies. Aucun corps de requête n’est nécessaire. La route ajoute 1 au champ copies du livre. Un livre qui n’a pas encore de champ copies se retrouve avec "copies": 1.
  2. Ajoute POST /books/:id/tags. Le corps de la requête indique une étiquette, par exemple { "tag": "classic" }. La route ajoute l’étiquette à la fin de la liste tags du livre et conserve les étiquettes déjà présentes. Un livre qui n’a pas encore de champ tags se retrouve avec "tags": ["classic"].
  3. Pour un identifiant invalide, les deux routes utilisent parseId, qui renvoie le code 400. Elles renvoient toutes deux le code 404 si aucun livre ne possède cet _id. Sinon, elles renvoient toutes deux le statut 200 et le livre entier mis à jour. Après avoir ajouté deux exemplaires et les étiquettes classic, puis favorite, Dune ressemble à ceci :
    { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi", "copies": 2, "tags": ["classic", "favorite"] }
    Essaie : Envoie GET /books et copie l’_id de Dune. Envoie POST /books/<Dune's _id>/copies avec un corps vide : tu obtiens Dune avec "copies": 1. Envoie la requête une nouvelle fois : "copies": 2. Envoie POST /books/<Dune's _id>/tags avec { "tag": "classic" } : "tags": ["classic"]. Envoie-la une nouvelle fois avec { "tag": "favorite" } : "tags": ["classic", "favorite"]. Envoie POST /books/000000000000000000000000/copies : tu obtiens le code 404.

Essayez vous-même

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

// Chaque erreur renvoie { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// Le :id sous forme d’ObjectId, ou null après avoir renvoyé 400 pour un id invalide.
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');
});

Toutes les leçons de Bases de données avec MongoDB

Entraînez-vous par vous-même : Playground Express