Index uniques
Fait partie de la section Bases de données avec MongoDB du Journey Node.js de Coddy. Leçon 17 sur 29.
Deux livres ayant le même ISBN ne devraient jamais être stockés tous les deux, et un findOne avant chaque insertion laisse une faille : deux requêtes peuvent ne rien trouver et toutes les deux insérer le livre.
Un index est une liste triée des valeurs d'un champ que la base de données conserve à côté d'une collection pour trouver rapidement des documents. Un index unique refuse également un second document ayant une valeur déjà présente : l'insertion lève une MongoServerError avec le code 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’adresse e-mail est déjà utilisée
}Crée l'index au démarrage, avant d'insérer les données. La création d'un index qui existe déjà ne fait rien.
Défi
FacileAucun livre ne peut avoir le même ISBN qu’un autre, même si deux requêtes arrivent au même instant. Laisse la base de données refuser le doublon.
- Dans
server.js, en haut du fichier, crée un index unique surisbn. Fais-le avant que le code de démarrage stocke les trois livres avecinsertMany, et attends sa fin avecawait. - Dans
POST /books, l’insertion déclenche désormais une erreur si l’ISBN est déjà enregistré. Intercepte cette erreur. Lorsque soncodevaut11000, renvoie le statut 409 et une erreur, par exemple{ "error": "That ISBN is already on the shelf" }. Relance toute autre erreur afin qu’Express renvoie une erreur 500. - Un livre avec un nouvel ISBN reçoit toujours le statut 201 et le livre enregistré, comme avant. Essaie : Envoie
POST /booksavec un deuxième exemplaire de Dune :{ "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }. Tu obtiens le statut 409 et l’erreur. EnvoieGET /books: tu obtiens toujours trois livres, et l’onglet Data affiche Dune une seule fois. EnvoiePOST /booksavec{ "title": "Beloved", "author": "Toni Morrison", "year": 1987, "isbn": "9781400033416", "genre": "literary" }: tu obtiens le statut 201.
Essayez vous-même
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());
// Chaque erreur renvoie { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// Le :id sous forme d’ObjectId, ou null après avoir renvoyé 400 si l’identifiant est invalide.
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');
});
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Bases de données avec MongoDB
1Documents
Un livre comme documentCollections et bases de donnéesSe connecter à MongoDBLire des documents stockésInsérer des documentsTrouver un documentRechercher par ObjectIdComparer des nombresFiltres de requêteChoisir les champs renvoyésTrier les résultats de requêteConserver les premiers résultatsProjection, tri et limite2Modifier les documents
Mettre à jour les documentsOpérateurs de mise à jourEntraînez-vous par vous-même : Playground Express