אינדקסים ייחודיים
חלק מהיחידה מסדי נתונים עם 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;
// כתובת האימייל כבר בשימוש
}צרו את האינדקס בעת ההפעלה, לפני הוספת הנתונים. יצירת אינדקס שכבר קיים אינה עושה דבר.
אתגר
קללא ייתכן שלשני ספרים יהיה אותו ISBN, גם אם שתי בקשות מגיעות באותו הרגע. תן למסד הנתונים לדחות את הכפילות.
- ב־
server.js, בראש הקובץ, צור אינדקס ייחודי עלisbn. עשה זאת לפני שקוד הפתיחה שומר את שלושת הספרים באמצעותinsertMany, והמתן לו בעזרתawait. - ב־
POST /books, פעולת ההוספה זורקת עכשיו שגיאה כשה־ISBN כבר שמור. תפוס את השגיאה הזאת. כאשר ה־codeשלה הוא11000, החזר סטטוס 409 ושגיאה, למשל{ "error": "That ISBN is already on the shelf" }. זרוק מחדש כל שגיאה אחרת, כדי ש־Express יחזיר 500. - ספר עם 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');
});
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה מסדי נתונים עם MongoDB
תרגלו בעצמכם: Express אונליין