Understanding Sequelize Pagination

Understanding the Problem

Imagine you're building a music database application that needs to display lists of musicians, bands, and instruments. When you have thousands of records, sending all the data at once can be overwhelming for both the server and the client. We need to implement pagination to break the data into manageable chunks.

The core challenges we need to solve are:

  1. Accepting and validating page and size parameters from API requests
  2. Converting these parameters into Sequelize's limit and offset values
  3. Implementing pagination across multiple endpoints consistently

Devising a Plan

Let's break this down into manageable steps:

  1. Step 1: Parse and validate query parameters
  2. Step 2: Convert to Sequelize parameters
  3. Step 3: Apply pagination to queries

Implementing the Solution

Basic Implementation


// GET /musicians endpoint
app.get('/musicians', async (req, res, next) => {
    // Parse query parameters with defaults
    let { page = 1, size = 5 } = req.query;
    
    // Convert to numbers since query params are strings
    page = Number(page);
    size = Number(size);
    
    // Calculate limit and offset
    const pagination = {};
    
    // Special case: page=0 means return all records
    if (page !== 0) {
        pagination.limit = size;
        pagination.offset = size * (page - 1);
    }
    
    // Query with pagination
    const musicians = await Musician.findAll({ 
        order: [['lastName'], ['firstName']], 
        attributes: ['id', 'firstName', 'lastName'],
        include: [{
            model: Band,
            attributes: ['id', 'name']
        }],
        ...pagination  // Spread the pagination options
    });

    res.json(musicians);
});
    

Advanced Implementation with Middleware


// Pagination middleware
const paginationMiddleware = (req, res, next) => {
    let { page = 1, size = 5 } = req.query;
    
    page = Number(page);
    size = Number(size);
    
    const pagination = {};
    
    if (page !== 0) {
        pagination.limit = size;
        pagination.offset = size * (page - 1);
    }
    
    // Attach pagination to request object
    req.pagination = pagination;
    next();
};

// Use middleware in routes
app.get('/musicians', paginationMiddleware, async (req, res, next) => {
    const musicians = await Musician.findAll({ 
        ...req.pagination,
        order: [['lastName'], ['firstName']], 
        attributes: ['id', 'firstName', 'lastName'],
        include: [{
            model: Band,
            attributes: ['id', 'name']
        }]
    });

    res.json(musicians);
});
    

Understanding the Code

Query Parameters

When making requests to your API, clients can include query parameters to control pagination:

Offset Calculation

The formula offset = size * (page - 1) works like this:

Real World Examples

Think of pagination like:

Common Pagination Patterns

There are several common ways to implement pagination in APIs:

Offset Pagination (What we implemented)

Uses page numbers and page sizes. Good for:

Cursor Pagination

Uses a pointer to the last seen record. Better for:

Best Practices

When implementing pagination, remember to:

Performance Considerations

When working with pagination, keep in mind: