Indici univoci
Fa parte della sezione Database con MongoDB del percorso Node.js di Coddy. Lezione 17 di 29.
Due libri con lo stesso ISBN non dovrebbero mai essere entrambi archiviati e un findOne prima di ogni inserimento lascia un margine di rischio: due richieste possono non trovare nulla ed eseguire entrambe l'inserimento.
Un indice è un elenco ordinato dei valori di un campo che il database mantiene accanto a una raccolta per trovare rapidamente i documenti. Un indice univoco rifiuta anche un secondo documento con un valore che contiene già: l'inserimento genera un MongoServerError con codice 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;
// l'indirizzo email è già in uso
}Crea l'indice all'avvio, prima di inserire i dati. Creare un indice già esistente non ha alcun effetto.
Sfida
FacileDue libri non possono avere lo stesso ISBN, nemmeno se due richieste arrivano nello stesso momento. Lascia che sia il database a rifiutare il duplicato.
- In
server.js, all'inizio, crea un indice univoco suisbn. Fallo prima che il codice iniziale memorizzi i tre libri coninsertManye attendi usandoawait. - In
POST /books, l'inserimento ora genera un errore se l'ISBN è già presente. Intercetta quell'errore. Quando il suocodeè11000, rispondi con lo stato 409 e un errore, ad esempio{ "error": "That ISBN is already on the shelf" }. Rilancia qualsiasi altro errore, in modo che Express risponda con 500. - Un libro con un nuovo ISBN riceve ancora lo stato 201 e il libro memorizzato, come prima. Prova: invia
POST /bookscon una seconda copia di Dune:{ "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }. Ricevi 409 e l'errore. InviaGET /books: ricevi ancora tre libri e la scheda Data mostra Dune una volta sola. InviaPOST /bookscon{ "title": "Beloved", "author": "Toni Morrison", "year": 1987, "isbn": "9781400033416", "genre": "literary" }: ricevi 201.
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.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 se 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;
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');
});
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
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