Gömme veya Referans Verme
Coddy'nin Node.js Journey'sinin MongoDB ile Veritabanları bölümünün bir parçası. Ders 29 / 29.
Yorumlar ve puanlar bir kitaba aittir, ancak her birini saklamak için en uygun yer farklıdır.
Gömme, ilişkili verileri belgenin içine koyar; böylece tek bir okumada kitaba ve yorumlarına ulaşılır. Üst öğesiyle birlikte okunan ve küçük kalan veriler için uygundur. Referans verme ise veriyi, ratings örneğinde olduğu gibi, üst öğenin kimliğiyle birlikte kendi koleksiyonunda tutar. Sınırsız büyüyen veya her puanın ortalaması gibi tek başına sorgulanan veriler için uygundur.
Bir belge en fazla 16 MB tutabilir, bu nedenle gömülü bir dizinin sınırı olmalıdır. $push içinde $each eklenecek değerleri listeler ve $slice: -3 son üç değeri tutar.
await players.updateOne(
{ name: 'Ada' },
{ $push: { recentScores: { $each: [120], $slice: -3 } } }
);Görev
KolayYorumlar kısadır, her zaman kitaplarıyla birlikte gösterilir ve bir kitapta yalnızca en yeni 5 yorum gösterilir. Bu nedenle yorumları kitap belgesinin içinde, bir reviews dizisinde tut ve bu dizinin hiçbir zaman 5 öğeyi aşmasına izin verme.
server.jsdosyasınaPOST /books/:id/reviewsekle. İstek gövdesi şöyle görünür:{ "user": "ada", "text": "Loved it" }. Geçersiz bir kimlik içinparseIdkullan; bu durumda 400 yanıtı verilir.- Tek bir güncellemeyle, örneğin
{ "user": "ada", "text": "Loved it" }yorumunu kitabınreviewsdizisinin sonuna ekle. Aynı güncellemede diziyi son 5 öğesine indir, böylece en eski yorum diziden çıkar. Yorumu olmayan bir kitap için yeni bir dizi oluşturulur. - Bu
_iddeğerine sahip bir kitap yoksa 404 yanıtı ver. - Aksi takdirde, 201 durum koduyla yalnızca kitabın
reviewsdizisini, kitabın tamamını değil, en eski yorum başta olacak şekilde yanıt olarak gönder. İlk yorumdan sonra yanıt şu olur:Deneyin:[ { "user": "ada", "text": "Loved it" } ]GET /booksisteği gönder ve Dune'un_iddeğerini kopyala.{ "user": "ada", "text": "Loved it" }içeriğiylePOST /books/<Dune's _id>/reviewsisteği gönder: 201 yanıtını ve yukarıdaki diziyi alırsın. Metinleri"Review 2"ile"Review 6"arasında olan beş yorum daha gönder. Son yanıt beş yorum içerir:Review 2ileReview 6;Loved itartık yoktur.GET /books/<Dune's _id>isteği gönder: Dune,reviewsiçinde aynı beş yorumu içerir ve Data sekmesi bunları kitabın içinde gösterir.
Kendin dene
import express from 'express';
import { z } from 'zod';
import { ObjectId } from 'mongodb';
import { books, ratings } from './db.js';
const app = express();
app.use(express.json());
// Her hata için { error: message } biçiminde yanıt verilir.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// :id geçerliyse ObjectId olarak döndürülür; geçersiz bir id için 400 yanıtı verildikten sonra null döndürülür.
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;
}
const BookInput = z.object({
title: z.string().min(1),
author: z.string().min(1),
year: z.number().int(),
isbn: z.string().min(1),
genre: z.string().min(1),
});
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 page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 10;
const items = await books
.find(filter)
.skip((page - 1) * limit)
.limit(limit)
.toArray();
const total = await books.countDocuments(filter);
res.json({ items, total });
});
app.post('/books', async (req, res) => {
const parsed = BookInput.safeParse(req.body);
if (!parsed.success) {
const issue = parsed.error.issues[0];
return sendError(res, 400, `${issue.path.join('.')}: ${issue.message}`);
}
const book = parsed.data;
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');
});
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
MongoDB ile Veritabanları bölümündeki tüm dersler
1Belgeler
Bir Belge Olarak KitapKoleksiyonlar ve VeritabanlarıMongoDB'ye BağlanmaSaklanan Belgeleri OkumaBelge EklemeTek Bir Belge BulmaObjectId ile BulmaSayıları KarşılaştırmaSorgu FiltreleriDöndürülen Alanları SeçmeSorgu Sonuçlarını Sıralamaİlk Sonuçları TutmaProjection, Sort ve Limit4Yapı
Bir Veritabanı ModülüVeritabanında SayfalamaKendi başına pratik yap: Express playground