Paginated meta contract
{
"data": [ /* up to 20 tickets */ ],
"meta": { "page": 1, "limit": 20, "total": 57, "totalPages": 3 }
}totalPages = Math.ceil(total / limit), so the frontend can render pager controls without recomputing it.
Topic
Definition
Pagination bounds a list response to a page of a requested size, and filtering narrows that page to rows matching client-supplied criteria, both expressed as validated query parameters that a service applies to its database query.
In simpler words
Instead of returning every ticket at once, the API returns one page at a time and can narrow that page by status or search text.
Ground this in ListTicketsQueryDto and the query-builder pagination in TicketsService.list.
Definition
ListTicketsQueryDto declares page, limit, an optional status enum, and an optional free-text q, each validated and coerced from query-string input, and TicketsService.list returns both the requested page of data and a meta object describing page, limit, total, and totalPages.
In simpler words
Clients ask for a page and a size; the server answers with that page plus enough numbers to build pager UI without a second request.
limit is capped at 100 with @Max — an unbounded limit would let a client request the entire table in one response.
meta.total comes from qb.getManyAndCount(), which runs the count and the row fetch together.
{
"data": [ /* up to 20 tickets */ ],
"meta": { "page": 1, "limit": 20, "total": 57, "totalPages": 3 }
}totalPages = Math.ceil(total / limit), so the frontend can render pager controls without recomputing it.
Definition
Filtering should apply constraints through parameterized query-builder conditions rather than concatenated strings, and each filter should be optional so its absence does not change the query’s meaning.
In simpler words
Every filter is an andWhere clause added only when the client actually sent that field.
status filters with an exact match; q performs a case-insensitive ILIKE search across title and description — both use bound parameters (:status, :q), never string interpolation.
Filters and pagination combine: skip/take is applied after the where clauses, so total and totalPages reflect the filtered set, not the whole table.
if (query.status) {
qb.andWhere('ticket.status = :status', { status: query.status });
}
if (query.q) {
qb.andWhere('(ticket.title ILIKE :q OR ticket.description ILIKE :q)', {
q: `%${query.q}%`,
});
}Bound parameters avoid SQL injection even though q is arbitrary client text.
Test
At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.
Checking your session…
10 questions · concept 3 · syntax 2 · practical 3 · logic 2