Upserts
Teil des Abschnitts Datenbanken mit MongoDB der Node.js-Journey von Coddy. Lektion 18 von 29.
Jeder Leser bewertet ein Buch einmal. Bei einer erneuten Bewertung sollten sich die Sterne ändern, statt eine zweite Bewertung hinzuzufügen.
Mit { upsert: true } aktualisiert updateOne das Dokument, auf das der Filter zutrifft, und fügt, wenn kein Dokument zutrifft, eines ein, das aus den Feldern des Filters und der Aktualisierung erstellt wird. Ein Aufruf deckt den ersten und alle weiteren Fälle ab.
// ein Einstellungsdokument pro Benutzer
await settings.updateOne(
{ user: 'ada' },
{ $set: { theme: 'dark' } },
{ upsert: true }
);Der Filter legt fest, was ein Dokument eindeutig macht; hier ist es ein Dokument pro Benutzer. upsertedId des Ergebnisses wird gesetzt, wenn der Aufruf ein Dokument eingefügt hat. Ein einfaches insertOne kann das nicht: Es fügt jedes Mal ein Dokument hinzu.
Aufgabe
EinfachLeser bewerten Bücher mit 1 bis 5 Sternen. Jeder Leser bewertet ein Buch nur einmal: Bei einer erneuten Bewertung werden die Sterne geändert, statt eine zweite Bewertung hinzuzufügen.
- Öffne in
server.jsnebenconst books = db.collection('books');eine zweite Collection namensratings. Jede Bewertung ist ein Dokument:Speichere{ "bookId": "6720a1f4c2b9e81d3a5f0c11", "user": "ada", "stars": 4 }bookIdals die ObjectId, dieparseIdzurückgibt, nicht als den Text aus der URL. In JSON sehen beide gleich aus. - Füge
PUT /books/:id/ratingshinzu. Der Body sieht so aus:{ "user": "ada", "stars": 4 }. Wenn dieser Nutzer dieses Buch bereits bewertet hat, ändere seine Sterne. Falls nicht, füge eine neue Bewertung hinzu. Erledige beides mit einem einzigen Aufruf. Antworte mit Status 200 und der Bewertung:{ "user": "ada", "stars": 4 }. - Füge
GET /books/:id/ratingshinzu. Die Route antwortet mit Status 200 und einem Array der Bewertungen dieses Buchs, so wie sie gespeichert sind. Bewertungen anderer Bücher dürfen nicht enthalten sein:[ { "_id": "6720a1f4c2b9e81d3a5f0c21", "bookId": "6720a1f4c2b9e81d3a5f0c11", "user": "ada", "stars": 2 } ] - Beide Routen verwenden bei einer ungültigen ID
parseId, das den Status 400 zurückgibt. Probiere es aus: SendeGET /booksund kopiere Dunes_id. SendePUT /books/<Dune's _id>/ratingsmit{ "user": "ada", "stars": 4 }: Du erhältst 200 und{ "user": "ada", "stars": 4 }. Sende es erneut mit{ "user": "ada", "stars": 2 }: Du erhältst 200 und{ "user": "ada", "stars": 2 }. SendeGET /books/<Dune's _id>/ratings: Du erhältst eine Bewertung, die von Ada, mit 2 Sternen. Bewerte Dune mit{ "user": "bo", "stars": 5 }und rufe die Liste erneut ab: Du erhältst zwei Bewertungen. Im Tab „Data“ wird jetzt eine Collectionratingsangezeigt.GET /books/<Kindred's _id>/ratingsgibt[]zurück.
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.createIndex({ isbn: 1 }, { unique: true });
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 wird { error: message } zurückgegeben.
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;
try {
await books.insertOne(book);
} catch (err) {
if (err.code === 11000) {
return sendError(res, 409, 'That ISBN is already on the shelf');
}
throw err;
}
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');
});
Alle Lektionen in Datenbanken mit MongoDB
1Dokumente
Ein Buch als DokumentSammlungen und DatenbankenVerbindung zu MongoDB herstellenGespeicherte Dokumente lesenDokumente einfügenEin Dokument findenÜber ObjectId suchenZahlen vergleichenAbfragefilterZurückgegebene Felder auswählenAbfrageergebnisse sortierenDie ersten Ergebnisse behaltenProjektion, Sortierung und LimitÜbe selbstständig: Express-Playground