@tusss/ood
    Preparing search index...

    Database Pagination & Sorting

    When building REST or GraphQL APIs, pagination and sorting are critical features for handling large datasets efficiently.

    The @tusss/ood package provides the Pagination, Sort, PaginationResult, and Paginable utilities to simplify query offset/limit calculations, validate sort parameters, and build standard, client-friendly responses.

    This recipe demonstrates how to integrate these classes with a database library (in this example, Prisma) to paginate and sort records.

    Let's write a route handler that fetches a list of users, applies pagination and sorting from the request query string, queries the database, and returns a paginated structure.

    Create a type for the properties of the model that the client is allowed to sort:

    // src/user/types.ts
    export type UserSortableFields = "id" | "name" | "createdAt";

    The service accepts a Pagination instance and a Sort instance, uses them to construct the Prisma query, and returns a Paginable container.

    // src/user/user.service.ts
    import { PrismaClient } from "@prisma/client";
    import { Pagination, Sort, PaginationResult, Paginable } from "@tusss/ood";
    import { UserSortableFields } from "./types";

    const prisma = new PrismaClient();

    export class UserService {
    async getUsers(
    pagination: Pagination,
    sort: Sort<UserSortableFields>
    ): Promise<Paginable> {
    // 1. Run database queries concurrently: fetch data and count total records
    const [users, total] = await Promise.all([
    prisma.user.findMany({
    // Map Pagination to Prisma's limit/offset configuration
    take: pagination.limit,
    skip: pagination.skip,

    // Map Sort to Prisma's sorting order
    orderBy: {
    [sort.field]: sort.direction,
    },
    }),
    prisma.user.count(),
    ]);

    // 2. Instantiate PaginationResult with pagination config and total count
    const paginationResult = new PaginationResult(pagination, total);

    // 3. Wrap records and metadata into a Paginable response container
    return new Paginable(users, paginationResult);
    }
    }

    Extract, sanitize, and validate request queries into Pagination and Sort instances:

    // src/user/user.controller.ts
    import { Request, Response } from "express";
    import { Pagination, Sort } from "@tusss/ood";
    import { UserService } from "./user.service";
    import { UserSortableFields } from "./types";

    const userService = new UserService();

    export async function listUsers(req: Request, res: Response) {
    try {
    // Parse pagination options (falls back to page=1, perPage=10 by default)
    const page = req.query.page ? Number(req.query.page) : undefined;
    const perPage = req.query.perPage ? Number(req.query.perPage) : undefined;
    const pagination = new Pagination({ page, perPage });

    // Parse sorting options
    const sortField = (req.query.sortBy as UserSortableFields) || "createdAt";
    const sortDir = req.query.sortOrder === "desc" ? "desc" : "asc";

    // Ensure only valid fields can be sorted
    const allowedFields: UserSortableFields[] = ["id", "name", "createdAt"];
    const field = allowedFields.includes(sortField) ? sortField : "id";

    const sort = new Sort<UserSortableFields>({
    field,
    direction: sortDir,
    });

    // Fetch paginated results
    const results = await userService.getUsers(pagination, sort);

    // Serialize cleanly back to client (toJSON() is supported under the hood)
    return res.status(200).json({
    success: true,
    data: results.data,
    pagination: results.pagination.toStruct(),
    });
    } catch (error) {
    return res.status(500).json({
    success: false,
    message: "Failed to load users",
    });
    }
    }

    Calling /users?page=2&perPage=5&sortBy=name&sortOrder=desc will yield:

    {
    "success": true,
    "data": [
    { "id": 12, "name": "Charlie", "createdAt": "2026-06-15T08:00:00.000Z" },
    { "id": 5, "name": "Bob", "createdAt": "2026-06-12T14:30:00.000Z" }
    ],
    "pagination": {
    "page": 2,
    "perPage": 5,
    "total": 12,
    "totalPages": 3,
    "nextPage": 3,
    "prevPage": 1
    }
    }