אינדקסים ייחודיים
חלק מהיחידה מסדי נתונים עם MongoDB במסלול ה-Node.js של Coddy. שיעור 9 מתוך 18.
לעולם אין לאחסן שני ספרים עם אותו 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. - בקשת
POST /booksעם ISBN שכבר שמור מחזירה409במקום לשמור עותק נוסף.
שלחו POST /books עם מספר ה־ISBN של Dune, 9780441013593. לאחר מכן פתחו את הכרטיסייה Data: הספר Dune מופיע שם פעם אחת.
נסו בעצמכם
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');
});
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.