TezXTezX
Middleware

paginationHandler

The paginationHandler middleware provides automatic pagination handling for API endpoints. It simplifies extracting pagination parameters, calculating offsets, generating metadata, and optionally integrates with a dynamic data source (e.g., database, ORM).

Import

import { paginationHandler } from "tezx/middleware";

Options

export type PaginationOptions<
  DataKey extends string = "data",
  CountKey extends string = "total",
  Item = any
> = {
  defaultPage?: number;       // Default page if not specified (default: 1)
  defaultLimit?: number;      // Default items per page (default: 10)
  maxLimit?: number;          // Maximum items per page (default: 100)
  queryKeyPage?: string;      // Query parameter for page (default: "page")
  queryKeyLimit?: string;     // Query parameter for limit (default: "limit")
  countKey?: CountKey;        // Key for total count (default: "total")
  dataKey?: DataKey;          // Key for data array (default: "data")

  getDataSource?: <T extends Record<string, any> = {}>(
    ctx: Context<T>,
    pagination: { page: number; limit: number; offset: number }
  ) => Promise<
    { [K in DataKey]: Item[] } & { [K in CountKey]: number }
  >;
};

Pagination Metadata

When pagination is applied, the following structure is available in ctx.pagination and response body:

type PaginationBodyType = {
  [x: string]: any;
  pagination: {
    page: number;          // Current page number
    limit: number;         // Items per page
    offset: number;        // Calculated offset
    totalItems: number;    // Total number of items
    totalPages: number;    // Total number of pages
    hasNextPage: boolean;  // Whether next page exists
    hasPrevPage: boolean;  // Whether previous page exists
    nextPage: number|null; // Next page number (or null)
    prevPage: number|null; // Previous page number (or null)
  };
};

Usage Examples

1. Basic Setup (No Data Source)

Extracts pagination parameters and makes them available via ctx.pagination.

import { paginationHandler } from "tezx/middleware";

app.get("/users", paginationHandler(), async (ctx) => {
  const { page, limit, offset } = ctx.pagination;
  ctx.json({ page, limit, offset });
});

// GET /users?page=2&limit=5
// → { "page": 2, "limit": 5, "offset": 5 }

2. With Dynamic Data Source (Database Integration)

Fetches data and total count from your database and returns a structured response.

app.get(
  "/products",
  paginationHandler({
    getDataSource: async (ctx, { page, limit, offset }) => {
      const { rows, count } = await Product.findAndCountAll({
        offset,
        limit,
      });
      return { data: rows, total: count };
    },
  })
);

Response example:

{
  "data": [ /* 10 products */ ],
  "total": 53,
  "pagination": {
    "page": 2,
    "limit": 10,
    "offset": 10,
    "totalItems": 53,
    "totalPages": 6,
    "hasNextPage": true,
    "hasPrevPage": true,
    "nextPage": 3,
    "prevPage": 1
  }
}

3. Custom Query Keys

app.get(
  "/articles",
  paginationHandler({
    queryKeyPage: "p",
    queryKeyLimit: "size",
  }),
  async (ctx) => {
    const { page, limit } = ctx.pagination;
    ctx.json({ page, limit });
  }
);

// GET /articles?p=3&size=20
// → { "page": 3, "limit": 20 }

4. Custom Data and Count Keys

app.get(
  "/comments",
  paginationHandler({
    dataKey: "items",
    countKey: "totalCount",
    getDataSource: async (ctx, { offset, limit }) => {
      const { rows, count } = await Comment.findAndCountAll({ offset, limit });
      return { items: rows, totalCount: count };
    },
  })
);

5. Strict Maximum Limit

app.get(
  "/logs",
  paginationHandler({
    defaultLimit: 20,
    maxLimit: 50, // Clients cannot request more than 50
  }),
  async (ctx) => {
    ctx.json({ limit: ctx.pagination.limit });
  }
);

// GET /logs?limit=500
// → limit will be capped at 50

Best Practices

  • Always enforce maxLimit to prevent abuse (e.g., requesting huge datasets).
  • Use consistent query keys (queryKeyPage, queryKeyLimit).
  • Custom response keys (dataKey, countKey) simplify frontend integration.
  • Place paginationHandler before your route handler so ctx.pagination is available.
  • If using getDataSource, the middleware sets the response automatically; otherwise, you can manually use ctx.pagination.