Upsert
Fa parte della sezione Database con MongoDB del percorso Node.js di Coddy. Lezione 18 di 29.
Ogni lettore valuta un libro una sola volta. Valutarlo di nuovo dovrebbe modificare le stelle, non aggiungere una seconda valutazione.
Con { upsert: true }, updateOne aggiorna il documento che corrisponde al filtro e, quando non ne corrisponde nessuno, ne inserisce uno creato dai campi del filtro più l’aggiornamento. Una sola chiamata copre la prima volta e tutte le volte successive.
// un documento delle impostazioni per ogni utente
await settings.updateOne(
{ user: 'ada' },
{ $set: { theme: 'dark' } },
{ upsert: true }
);Il filtro specifica cosa rende un documento univoco; in questo caso, un documento per utente. upsertedId del risultato viene impostato quando la chiamata ha inserito un documento. Un semplice insertOne non può farlo: aggiunge un documento ogni volta.
Sfida
FacileI lettori assegnano ai libri da 1 a 5 stelle. Ogni lettore valuta un libro una sola volta: valutarlo di nuovo modifica il numero di stelle invece di aggiungere una seconda valutazione.
- In
server.js, accanto aconst books = db.collection('books');, apri una seconda raccolta chiamataratings. Ogni valutazione è un documento:Memorizza{ "bookId": "6720a1f4c2b9e81d3a5f0c11", "user": "ada", "stars": 4 }bookIdcome ObjectId restituito daparseId, non come testo proveniente dall'URL. In JSON hanno lo stesso aspetto. - Aggiungi
PUT /books/:id/ratings. Il corpo ha questo aspetto:{ "user": "ada", "stars": 4 }. Se questo utente ha già valutato il libro, modifica il numero di stelle. In caso contrario, aggiungi una nuova valutazione. Esegui entrambe le operazioni con una sola chiamata. Rispondi con lo stato 200 e la valutazione:{ "user": "ada", "stars": 4 }. - Aggiungi
GET /books/:id/ratings. Risponde con lo stato 200 e un array contenente le valutazioni di quel libro così come sono memorizzate. Le valutazioni di altri libri non devono comparire:[ { "_id": "6720a1f4c2b9e81d3a5f0c21", "bookId": "6720a1f4c2b9e81d3a5f0c11", "user": "ada", "stars": 2 } ] - Entrambi gli endpoint usano
parseIdin caso di ID non valido, che restituisce 400. Prova: inviaGET /bookse copia l'_iddi Dune. InviaPUT /books/<Dune's _id>/ratingscon{ "user": "ada", "stars": 4 }: ottieni 200 e{ "user": "ada", "stars": 4 }. Invialo di nuovo con{ "user": "ada", "stars": 2 }: ottieni 200 e{ "user": "ada", "stars": 2 }. InviaGET /books/<Dune's _id>/ratings: ottieni una valutazione, quella di Ada, con 2 stelle. Valuta Dune con{ "user": "bo", "stars": 5 }e richiedi di nuovo l'elenco: ottieni due valutazioni. La scheda Data ora mostra una raccoltaratings.GET /books/<Kindred's _id>/ratingsrestituisce[].
Provalo tu
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());
// Ogni errore restituisce { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// Restituisce :id come ObjectId, oppure null dopo aver risposto con 400 per un id non valido.
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');
});
Tutte le lezioni di Database con MongoDB
1Documenti
Un libro come documentoRaccolte e databaseConnessione a MongoDBLettura dei documenti memorizzatiInserimento di documentiRicerca di un documentoRicerca tramite ObjectIdConfronto tra numeriFiltri di queryScelta dei campi restituitiOrdinamento dei risultati della queryMantenere i primi risultatiProiezione, ordinamento e limite2Modifica dei documenti
Aggiornamento dei documentiOperatori di aggiornamentoEsercitati da solo: Playground Express