0
0
ExpressHow-ToBeginner · 4 min read

How to Implement Cursor Pagination in Express Easily

To implement cursor pagination in Express, use a unique, ordered field like an ID or timestamp as the cursor to fetch the next set of results. Pass this cursor as a query parameter, then query your database for records greater than the cursor value, limiting the number of results returned.
📐

Syntax

Cursor pagination uses a cursor parameter to mark the last item fetched. The server queries for items with a key greater than this cursor and limits the results.

  • cursor: The last item's unique identifier or timestamp.
  • limit: Number of items to fetch per request.
  • Database query filters items where key > cursor.
javascript
app.get('/items', async (req, res) => {
  const { cursor, limit = 10 } = req.query;
  const query = cursor ? { _id: { $gt: new ObjectId(cursor) } } : {};
  const items = await db.collection('items')
    .find(query)
    .sort({ _id: 1 })
    .limit(Number(limit))
    .toArray();
  const nextCursor = items.length ? items[items.length - 1]._id : null;
  res.json({ items, nextCursor });
});
💻

Example

This example shows a simple Express route that returns paginated items from a MongoDB collection using cursor pagination. It returns items after the given cursor and includes the nextCursor for the next page.

javascript
import express from 'express';
import { MongoClient, ObjectId } from 'mongodb';

const app = express();
const client = new MongoClient('mongodb://localhost:27017');
let db;

async function start() {
  await client.connect();
  db = client.db('testdb');

  app.get('/items', async (req, res) => {
    try {
      const { cursor, limit = 5 } = req.query;
      const query = cursor ? { _id: { $gt: new ObjectId(cursor) } } : {};
      const items = await db.collection('items')
        .find(query)
        .sort({ _id: 1 })
        .limit(Number(limit))
        .toArray();

      const nextCursor = items.length ? items[items.length - 1]._id.toString() : null;
      res.json({ items, nextCursor });
    } catch (err) {
      res.status(500).json({ error: 'Server error' });
    }
  });

  app.listen(3000, () => console.log('Server running on http://localhost:3000'));
}

start();
Output
{"items":[{"_id":"64b8f1a2e1f1a2b3c4d5e6f7","name":"Item 1"},{"_id":"64b8f1a2e1f1a2b3c4d5e6f8","name":"Item 2"}],"nextCursor":"64b8f1a2e1f1a2b3c4d5e6f8"}
⚠️

Common Pitfalls

  • Using non-unique or non-ordered fields as cursors can cause duplicate or missing items.
  • Not converting cursor strings to the correct type (e.g., ObjectId) causes query errors.
  • Forgetting to sort results consistently leads to unpredictable pagination.
  • Not returning the nextCursor makes it hard for clients to request the next page.
javascript
/* Wrong: Using string cursor without conversion */
const query = cursor ? { _id: { $gt: cursor } } : {};

/* Right: Convert cursor to ObjectId for MongoDB */
const query = cursor ? { _id: { $gt: new ObjectId(cursor) } } : {};
📊

Quick Reference

  • Use a unique, ordered field (like _id) as cursor.
  • Convert cursor to correct type before querying.
  • Sort results ascending by cursor field.
  • Limit results to control page size.
  • Return nextCursor for client to fetch next page.

Key Takeaways

Use a unique, ordered field as the cursor to paginate reliably.
Always convert the cursor parameter to the correct type before querying.
Sort your query results consistently to avoid missing or duplicate items.
Return the next cursor value so clients can request subsequent pages.
Limit the number of results per request to control response size.