Paginazione nel database
Fa parte della sezione Database con MongoDB del percorso Node.js di Coddy. Lezione 27 di 29.
Uno scaffale con diecimila libri non può essere restituito tutto in un'unica risposta. Il client chiede una pagina alla volta e il database dovrebbe inviare solo quella pagina.
skip(n) salta i primi n risultati e limit(n) mantiene i successivi n. La pagina 1 inizia dall'inizio, quindi la pagina p salta (p - 1) * size. Invia il totale insieme alla pagina, calcolandolo con lo stesso filtro, così il client sa quante pagine ci sono.
// pagina 3 del registro degli errori, 20 voci per pagina
const entries = await log
.find({ level: 'error' })
.skip((3 - 1) * 20)
.limit(20)
.toArray();
const total = await log.countDocuments({ level: 'error' });Sfida
FacileUno scaffale di diecimila libri non può essere inviato in un'unica risposta. GET /books ora invia una pagina alla volta. Indica anche quanti libri corrispondono in totale, così un client può mostrare "pagina 1 di 5".
- In
server.js, modificaGET /books. Mantieni i filtriauthor,frometo. - Leggi
?page=(predefinito 1) e?limit=(predefinito 10). Se uno dei due manca o non è un numero, usa il valore predefinito. La pagina 1 inizia dal primo libro, quindi la paginapsalta(p - 1) * limitlibri e poi restituisce i successivilimitlibri. Lascia che sia il database a saltare e limitare i risultati. - Conta
totalnel database, usando lo stesso filtro. Conta tutti i libri corrispondenti, non solo quelli di questa pagina. - Rispondi con lo stato 200 e un oggetto, non un array.
GET /books?limit=2restituisce:Prova: invia{ "items": [ { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }, { "_id": "6720a1f4c2b9e81d3a5f0c12", "title": "Kindred", "author": "Octavia E. Butler", "year": 1979, "isbn": "9780807083697", "genre": "scifi" } ], "total": 3 }GET /books?limit=2: ottieni Dune e Kindred, con"total": 3. InviaGET /books?limit=2&page=2: ottieni solo Emma, sempre con"total": 3. InviaGET /books?limit=2&page=3: ottieni"items": []e"total": 3. InviaGET /books?author=Jane%20Austen&limit=1: ottieni Emma, con"total": 1. InviaGET /books: ottieni tutti e tre i libri initems.
Provalo tu
import express from 'express';
import { ObjectId } from 'mongodb';
import { books, ratings } from './db.js';
const app = express();
app.use(express.json());
// Ogni errore restituisce { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// Il :id come ObjectId, oppure null dopo aver restituito 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.put('/books/:id/ratings', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const { user, stars } = req.body;
await ratings.updateOne({ bookId: id, user }, { $set: { stars } }, { upsert: true });
res.json({ user, stars });
});
app.get('/books/:id/ratings', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
res.json(await ratings.find({ bookId: id }).toArray());
});
app.get('/stats', async (req, res) => {
const filter = req.query.genre ? { genre: req.query.genre } : {};
res.json({
books: await books.countDocuments(filter),
ratings: await ratings.countDocuments(),
});
});
app.get('/stats/genres', async (req, res) => {
const genres = await books
.aggregate([{ $group: { _id: '$genre', count: { $sum: 1 } } }])
.toArray();
res.json(genres);
});
app.get('/stats/top', async (req, res) => {
const top = await ratings
.aggregate([
{ $group: { _id: '$bookId', avg: { $avg: '$stars' }, count: { $sum: 1 } } },
{ $match: { count: { $gte: 2 } } },
{ $sort: { avg: -1 } },
{ $limit: 3 },
])
.toArray();
res.json(top);
});
app.get('/books/:id/full', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const [book] = await books
.aggregate([
{ $match: { _id: id } },
{
$lookup: {
from: 'authors',
localField: 'author',
foreignField: 'name',
as: 'authorInfo',
},
},
])
.toArray();
if (!book) {
return sendError(res, 404, 'No such book');
}
res.json(book);
});
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 limite4Struttura
Un modulo per il databasePaginazione nel databaseEsercitati da solo: Playground Express