Contar documentos
Parte de la sección Bases de datos con MongoDB del Journey de Node.js de Coddy. Lección 19 de 29.
Un panel necesita números, y cargar todos los documentos solo para leer la longitud del arreglo hace que la base de datos los envíe todos.
countDocuments(filter) responde cuántos documentos coinciden, sin enviar ninguno. Si no hay filtro, cuenta toda la colección.
const open = await tickets.countDocuments({ status: 'open' });
const all = await tickets.countDocuments();Construye el filtro a partir de la consulta, como hace GET /books, y pasa ese mismo filtro al recuento. Un recuento que ignora el filtro da el número correcto para la pregunta equivocada: todos los tickets, cuando el cliente pidió los abiertos.
Desafío
FácilUn panel muestra cuántos libros y valoraciones hay. Cuéntalos en la base de datos en lugar de cargar todos los documentos.
- En
server.js, debajo del comentario// Add GET /stats here., añade una rutaGET /stats. - La ruta responde con el estado 200 y la cantidad de documentos de cada colección. Con los libros iniciales y sin valoraciones, el resultado es:
{ "books": 3, "ratings": 0 } ?genre=scifihace quebookscuente solo los libros de ese género.ratingssiempre cuenta todas las valoraciones, sin importar el género. Pruébalo: EnvíaGET /stats: obtienes{ "books": 3, "ratings": 0 }. EnvíaGET /stats?genre=scifi:{ "books": 2, "ratings": 0 }. EnvíaGET /stats?genre=poetry:{ "books": 0, "ratings": 0 }. Copia el_idde Dune deGET /booksy luego envíaPUT /books/<Dune's _id>/ratingscon{ "user": "ada", "stars": 4 }. EnvíaGET /statsotra vez:{ "books": 3, "ratings": 1 }.
Pruébalo tú mismo
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');
const ratings = db.collection('ratings');
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());
// Todos los errores devuelven { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// El :id como ObjectId, o null después de responder con 400 si el id no es válido.
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());
});
// Añade GET /stats aquí.
app.listen(3000, () => {
console.log('Bookshelf is running on port 3000');
});
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Bases de datos con MongoDB
1Documentos
Un libro como documentoColecciones y bases de datosConexión a MongoDBLectura de documentos almacenadosInserción de documentosBúsqueda de un documentoBúsqueda por ObjectIdComparación de númerosFiltros de consultaSelección de los campos devueltosOrdenación de los resultados de consultaConservar los primeros resultadosProyección, ordenación y límite2Modificación de documentos
Actualización de documentosOperadores de actualizaciónPractica por tu cuenta: Playground de Express