Menu
CoddyTech

Indeksy unikatowe

Część sekcji Bazy danych z MongoDB ścieżki Node.js w Coddy. Lekcja 17 z 29.

Nie należy przechowywać dwóch książek z tym samym numerem ISBN, a wykonanie findOne przed każdym wstawieniem pozostawia lukę: dwa żądania mogą jednocześnie niczego nie znaleźć i oba wstawić dokument.

Indeks to posortowana lista wartości pola, którą baza danych przechowuje obok kolekcji, aby szybko znajdować dokumenty. Unikalny indeks odrzuca również drugi dokument z wartością, którą już zawiera: wstawienie powoduje zgłoszenie błędu MongoServerError z kodem 11000, „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;
    // ten adres e-mail jest już zajęty
}

Utwórz indeks podczas uruchamiania, zanim zostaną dodane dane. Utworzenie indeksu, który już istnieje, nic nie zmienia.

challenge icon

Wyzwanie

Łatwy

Żadne dwie książki nie mogą mieć tego samego numeru ISBN, nawet jeśli dwa żądania nadejdą w tej samej chwili. Niech baza danych odrzuci duplikat.

  1. W pliku server.js, na samej górze, utwórz unikatowy indeks dla isbn. Zrób to przed zapisaniem przez kod początkowy trzech książek za pomocą insertMany i zaczekaj na zakończenie operacji, używając await.
  2. W POST /books operacja wstawiania zgłasza teraz błąd, gdy numer ISBN jest już zapisany. Przechwyć ten błąd. Gdy jego code ma wartość 11000, odpowiedz statusem 409 i błędem, na przykład { "error": "That ISBN is already on the shelf" }. Zgłoś ponownie każdy inny błąd, aby Express odpowiedział statusem 500.
  3. Książka z nowym numerem ISBN nadal otrzymuje status 201 i jest zapisywana, tak jak wcześniej. Wypróbuj: Wyślij POST /books z drugą kopią książki Dune: { "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }. Otrzymasz status 409 i błąd. Wyślij GET /books: nadal otrzymasz trzy książki, a karta Data pokaże Dune tylko raz. Wyślij POST /books z { "title": "Beloved", "author": "Toni Morrison", "year": 1987, "isbn": "9781400033416", "genre": "literary" }: otrzymasz status 201.

Spróbuj swoich sił

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

// Każdy błąd zwraca odpowiedź w postaci { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// Parametr :id jako ObjectId lub null po zwróceniu odpowiedzi 400 dla nieprawidłowego 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);
});

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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Bazy danych z MongoDB

Poćwicz samodzielnie: Edytor online Express