Combine Search, Filtering and Pagination

A useful production-style list endpoint should support search, filters, sorting, and pagination together.

Example:

GET /api/v1/users?page=1&limit=10&search=john&role=user&sortBy=createdAt&order=desc

The request should:

  1. Parse query parameters.
  2. Validate the parameters.
  3. Build the MongoDB filter.
  4. Build the sorting options.
  5. Apply pagination.
  6. Retrieve matching records.
  7. Count matching records.
  8. Return pagination metadata.

The count query must use the same filter as the data query.

Use lean() for Read-Only Queries

Mongoose lean() can reduce memory overhead when you do not need document methods, getters, or change tracking.

Combined User Query Service
export async function listUsers({
  page,
  limit,
  filter,
  sort
}) {
  const skip = (page - 1) * limit;

  const [users, total] = await Promise.all([
    User.find(filter)
      .select('-password')
      .sort(sort)
      .skip(skip)
      .limit(limit)
      .lean(),
    User.countDocuments(filter)
  ]);

  return {
    users,
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit)
  };
}

Combines filtering, sorting, pagination, and counting. lean() returns plain JavaScript objects rather than full Mongoose documents, which is useful for read-only results.

Key Takeaways
  • Combine query features into a consistent service interface.

  • Apply identical filters to data and count queries.

  • Use lean() when full Mongoose document behavior is unnecessary.