Pagination & sorting
The findAll() return shape, default sort, and the sortBy/sortOrder options.
findAll(filters?, pagination?, options?) returns a paginated result envelope — not a bare array.
Return shape
type FindAllResult<T> = {
data: T[];
total: number; // total rows matching the filter, ignoring pagination
page: number; // echo of the page you requested (1-indexed)
limit: number; // echo of the limit you requested, after capping
};const { data, total, page, limit } = await service.findAll(
{ status: 'active' },
{ page: 2, limit: 25 },
{ search: { term: 'john', columns: ['name', 'email'] } },
);
// data: 25 active users matching "john" for page 2
// total: 240 active users matching "john"
// page: 2
// limit: 25The pagination argument is optional. When omitted, findAll uses
defaults.pagination.defaultLimit (default 20) and starts at page 1.
total is the row count after filters and options.search, but before
pagination, so it stays correct across pages.
Pagination options
| Field | Type | Default | Notes |
|---|---|---|---|
page | number | 1 | 1-indexed. |
limit | number | defaults.pagination.defaultLimit | Capped at defaults.pagination.maxLimit (default 100). |
sortBy | string | string[] | defaultSort (entity) | Column name(s). |
sortOrder | 'asc' | 'desc' | 'desc' | Applied when sortBy is a string. |
Sorting
Explicit sortBy
await service.findAll(
{},
{ sortBy: 'createdAt', sortOrder: 'desc' },
);sortBy accepts a string or an array. When an array is passed, sortOrder is ignored — each column carries its own order via the entity's defaultSort definition (see below).
Default sort per entity
Define an ordered list of columns in forFeature config — they're applied as the ORDER BY clause when no explicit sortBy is passed. The first column is the primary sort, the rest are tiebreakers.
DrizzleCrudModule.forFeature([
{
service: PostsService,
table: posts,
config: {
defaultSort: [
{ column: 'position', order: 'asc' },
{ column: 'created_at', order: 'desc' }, // tiebreaker
],
},
},
])// ORDER BY position ASC, created_at DESC
await service.findAll();
// ORDER BY name DESC — the default is replaced, not appended
await service.findAll({}, { sortBy: 'name' });sortBy replaces defaultSort
An explicit sortBy replaces the entity's defaultSort entirely.
Pass an array to sortBy if you want the same multi-column ordering
behavior.
Opt-in created_at fallback
Set defaults.sortOrder in forRoot to apply a created_at fallback to every entity that doesn't define its own defaultSort:
DrizzleCrudModule.forRoot({
dialect: 'postgresql',
connectionString: process.env.DATABASE_URL,
schema,
defaults: {
sortOrder: 'desc', // every findAll() orders by created_at DESC unless overridden
},
});Per-column order defaults to 'asc' when you don't specify it. Unknown columns in defaultSort are skipped and emit a [nestjs-drizzle-crud] defaultSort references unknown column "<col>"; skipping. warning at startup — they never throw. (Caller-supplied sortBy is different: it throws on unknown columns, see below.)
Cap on limit
limit is capped at defaults.pagination.maxLimit. Anything over the cap is silently lowered to the cap. To return more rows per page, raise the cap in forRoot:
defaults: {
pagination: { defaultLimit: 20, maxLimit: 200 },
}The cap is enforced on every call. If you genuinely need to return every row
(e.g. an export endpoint), use a custom method that bypasses findAll and
calls Drizzle directly.
Clamping & fail-fast
Pagination and sorting inputs are normalized before they reach SQL. Bad input is either silently clamped to a safe value or rejected as BadRequestException (→ 400), depending on the failure mode.
Silently clamped (never throws):
| Input | Result |
|---|---|
page < 1 | 1 |
limit <= 0 | 1 |
limit > maxLimit | maxLimit |
page / limit not a finite number | 1 / defaultLimit |
A LIMIT 0 or negative OFFSET can never reach SQL — every value is clamped via the package's shared resolvePagination helper.
Throws BadRequestException (→ 400):
| Input | Why |
|---|---|
Caller sortBy not a column on the table | Mirrors the unknown-column guard — typos surface immediately instead of returning unsorted data. |
Caller sortOrder other than 'asc' / 'desc' | Without the guard, anything other than 'desc' silently sorted ascending. |
{ gt / gte / lt / lte: NaN } or { …: Infinity } | Reaches SQL as an opaque 500; rejected as 400 instead. Affects numeric columns only — date / string operands pass through unchanged. |
For the full per-method exception table (404 / 409 / 400 / 500 mapping), see Errors.
Cursor-based pagination
The package does not ship cursor-based pagination out of the box. findAll is offset-based. For large datasets, write a custom method that uses Drizzle's where(...).orderBy(...).limit(...) chain with a cursor column (e.g. id > lastSeenId).