Menu
CoddyTech

אינדקסים ייחודיים

חלק מהיחידה מסדי נתונים עם MongoDB במסלול ה-Node.js של Coddy. שיעור 17 מתוך 29.

אין לאחסן לעולם שני ספרים עם אותו ISBN, ובדיקת findOne לפני כל הוספה משאירה פתח: שתי בקשות יכולות למצוא שאין תוצאה, ושתיהן יכולות להוסיף את הספר.

אינדקס הוא רשימה ממוינת של ערכי שדה, שמסד הנתונים שומר לצד אוסף כדי למצוא מסמכים במהירות. אינדקס ייחודי גם מסרב לקבל מסמך נוסף עם ערך שכבר קיים בו: פעולת ההוספה זורקת MongoServerError עם הקוד 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;
    // כתובת האימייל כבר בשימוש
}

צרו את האינדקס בעת ההפעלה, לפני הוספת הנתונים. יצירת אינדקס שכבר קיים אינה עושה דבר.

challenge icon

אתגר

קל

לא ייתכן שלשני ספרים יהיה אותו ISBN, גם אם שתי בקשות מגיעות באותו הרגע. תן למסד הנתונים לדחות את הכפילות.

  1. ב־server.js, בראש הקובץ, צור אינדקס ייחודי על isbn. עשה זאת לפני שקוד הפתיחה שומר את שלושת הספרים באמצעות insertMany, והמתן לו בעזרת await.
  2. ב־POST /books, פעולת ההוספה זורקת עכשיו שגיאה כשה־ISBN כבר שמור. תפוס את השגיאה הזאת. כאשר ה־code שלה הוא 11000, החזר סטטוס 409 ושגיאה, למשל { "error": "That ISBN is already on the shelf" }. זרוק מחדש כל שגיאה אחרת, כדי ש־Express יחזיר 500.
  3. ספר עם ISBN חדש עדיין מקבל 201 ואת הספר השמור, כמו קודם. נסה זאת: שלח POST /books עם עותק שני של Dune: { "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }. תקבל 409 ואת השגיאה. שלח GET /books: עדיין תקבל שלושה ספרים, ולשונית הנתונים תציג את Dune פעם אחת. שלח POST /books עם { "title": "Beloved", "author": "Toni Morrison", "year": 1987, "isbn": "9781400033416", "genre": "literary" }: תקבל 201.

נסו בעצמכם

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

// כל שגיאה מחזירה { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// החזרת :id בתור ObjectId, או null לאחר החזרת תשובת 400 עבור 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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה מסדי נתונים עם MongoDB

תרגלו בעצמכם: Express אונליין