Menu
CoddyTech

Eindeutige Indizes

Teil des Abschnitts Datenbanken mit MongoDB der Node.js-Journey von Coddy. Lektion 17 von 29.

Zwei Bücher mit derselben ISBN sollten niemals beide gespeichert werden, und ein findOne vor jedem Einfügen lässt eine Lücke: Zwei Anfragen können beide nichts finden und beide einfügen.

Ein Index ist eine sortierte Liste der Werte eines Feldes, die die Datenbank neben einer Collection speichert, damit Dokumente schnell gefunden werden. Ein eindeutiger Index lehnt außerdem ein zweites Dokument mit einem Wert ab, den er bereits enthält: Beim Einfügen wird ein MongoServerError mit dem Code 11000 ausgelöst: „E11000 duplicate key error“.

await users.createIndex({ email: 1 }, { unique: true });

try {
    await users.insertOne({ email: 'ada@example.com' });
} catch (err) {
    if (err.code !== 11000) throw err;
    // Die E-Mail-Adresse ist bereits vergeben
}

Erstelle den Index beim Start, bevor die Daten eingefügt werden. Wenn du einen bereits vorhandenen Index erstellst, geschieht nichts.

challenge icon

Aufgabe

Einfach

Kein Buch darf dieselbe ISBN haben, auch wenn zwei Anfragen gleichzeitig eintreffen. Lass die Datenbank das Duplikat ablehnen.

  1. Erstelle in server.js ganz oben einen eindeutigen Index für isbn. Mache das, bevor der Starter die drei Bücher mit insertMany speichert, und warte mit await darauf.
  2. In POST /books löst das Einfügen jetzt einen Fehler aus, wenn die ISBN bereits gespeichert ist. Fange diesen Fehler ab. Wenn sein code 11000 ist, antworte mit Status 409 und einem Fehler, zum Beispiel { "error": "That ISBN is already on the shelf" }. Löse jeden anderen Fehler erneut aus, damit Express mit 500 antwortet.
  3. Ein Buch mit einer neuen ISBN erhält weiterhin wie zuvor den Status 201 und das gespeicherte Buch. Probiere es aus: Sende POST /books mit einem zweiten Exemplar von Dune: { "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }. Du erhältst 409 und den Fehler. Sende GET /books: Du erhältst weiterhin drei Bücher, und der Data-Tab zeigt Dune einmal an. Sende POST /books mit { "title": "Beloved", "author": "Toni Morrison", "year": 1987, "isbn": "9781400033416", "genre": "literary" }: Du erhältst 201.

Probier es selbst

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

// Bei jedem Fehler lautet die Antwort { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// Die :id als ObjectId oder null, nachdem bei einer ungültigen id mit 400 geantwortet wurde.
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.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.listen(3000, () => {
    console.log('Bookshelf is running on port 3000');
});
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Datenbanken mit MongoDB

Übe selbstständig: Express-Playground