Menu
CoddyTech

Projection, Sort and Limit

Part of the Databases with MongoDB section of Coddy's Node.js journey. Lesson 13 of 29.

A "newest books" list needs a few fields from a few books, in order.

A projection picks the fields a query answers: 1 keeps a field, and _id comes along unless you set it to 0. On the cursor, sort({ field: -1 }) orders from high to low (1 is low to high), and limit(n) keeps the first n results.

// the 5 highest scores, name and score only
await players
    .find({}, { projection: { name: 1, score: 1, _id: 0 } })
    .sort({ score: -1 })
    .limit(5)
    .toArray();

The database sorts before it limits, in whatever order you chain the two calls. Sorting the array in JavaScript after limit sorts the wrong five.

challenge icon

Challenge

Easy

This step opens the course’s prepared bookshelf API project, rather than your project files from Query Filters (including server.js). Your earlier work stays in its own lesson. Read the supplied files before editing server.js. This copy contains the bookshelf routes and year-range filter.

The home page shows the newest books, with only their title and year.

  1. In server.js, add a GET /books/newest route. Put it above GET /books/:id. Express tries routes in order, and /books/:id also matches /books/newest. It would treat newest as an id and respond with 400.
  2. The route returns books sorted by year, highest first.
  3. ?n= says how many books to return. When n is missing, return 3.
  4. Each book holds only title and year, with no _id and no other field. Let the query pick the fields, sort the books and cut the list. For the starter's books, GET /books/newest?n=2 returns:
    [ { "title": "Kindred", "year": 1979 }, { "title": "Dune", "year": 1965 } ]
    Try it: Send GET /books/newest. You get Kindred (1979), Dune (1965) and Emma (1815), in that order, each with only a title and a year. Send GET /books/newest?n=1: you get [ { "title": "Kindred", "year": 1979 } ]. If you get 400, GET /books/:id took the request, so move your route above it.

Try it yourself

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());

// Every error answers { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

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/:id', async (req, res) => {
    if (!ObjectId.isValid(req.params.id)) {
        return sendError(res, 400, 'Not a valid id');
    }
    const book = await books.findOne({ _id: new ObjectId(req.params.id) });
    if (!book) {
        return sendError(res, 404, 'No such book');
    }
    res.json(book);
});

app.listen(3000, () => {
    console.log('Bookshelf is running on port 3000');
});
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Databases with MongoDB

Practice on your own: Express playground