Menu
CoddyTech

Veritabanında Sayfalama

Coddy'nin Node.js Journey'sinin MongoDB ile Veritabanları bölümünün bir parçası. Ders 27 / 29.

On bin kitaplık tek bir yanıtta gönderilemez. İstemci her seferinde bir sayfa ister ve veritabanı yalnızca o sayfayı göndermelidir.

skip(n) ilk n eşleşmenin üzerinden geçer ve limit(n) sonraki n eşleşmeyi tutar. 1. sayfa başlangıçtan başlar; bu nedenle p. sayfa (p - 1) * size kadar atlar. Toplamı da sayfayla birlikte, aynı filtreyi kullanarak sayın; böylece istemci kaç sayfa olduğunu bilir.

// hata günlüğünün 3. sayfası, sayfa başına 20 kayıt
const entries = await log
    .find({ level: 'error' })
    .skip((3 - 1) * 20)
    .limit(20)
    .toArray();
const total = await log.countDocuments({ level: 'error' });
challenge icon

Görev

Kolay

On bin kitaplık bir raf tek bir yanıtta gönderilemez. GET /books artık her seferinde bir sayfa gönderiyor. Ayrıca toplamda kaç kitabın eşleştiğini de bildiriyor; böylece istemci "page 1 of 5" gösterebilir.

  1. server.js dosyasında GET /books yolunu değiştir. author, from ve to filtrelerini koru.
  2. ?page= değerini oku (varsayılan 1) ve ?limit= değerini oku (varsayılan 10). Bunlardan biri eksikse veya bir sayı değilse varsayılan değerini kullan. 1. sayfa ilk kitaptan başlar; bu nedenle p sayfası (p - 1) * limit kitap atlar ve ardından sonraki limit kitabı döndürür. Atlamayı ve kesmeyi veritabanına yaptır.
  3. Aynı filtreyi kullanarak veritabanındaki total değerini hesapla. Bu değer yalnızca bu sayfadaki kitapları değil, eşleşen tüm kitapları sayar.
  4. 200 durum koduyla bir dizi değil, bir nesne döndür. GET /books?limit=2 şu yanıtı verir:
    {
      "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
    }
    Deneyin: GET /books?limit=2 isteğini gönder: "total": 3 ile birlikte Dune ve Kindred kitaplarını alırsın. GET /books?limit=2&page=2 isteğini gönder: yine "total": 3 ile birlikte yalnızca Emma kitabını alırsın. GET /books?limit=2&page=3 isteğini gönder: "items": [] ve "total": 3 değerlerini alırsın. GET /books?author=Jane%20Austen&limit=1 isteğini gönder: "total": 1 ile birlikte Emma kitabını alırsın. GET /books isteğini gönder: items içinde üç kitabın tamamını alırsın.

Kendin dene

import express from 'express';
import { ObjectId } from 'mongodb';
import { books, ratings } from './db.js';

const app = express();
app.use(express.json());

// Her hata { error: message } biçiminde yanıt verir.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// :id değerini ObjectId olarak döndürür; geçersiz bir id için 400 yanıtı verdikten sonra null döndürü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;
}

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');
});

MongoDB ile Veritabanları bölümündeki tüm dersler

Kendi başına pratik yap: Express playground