Filtering, sorting, and pagination must use deterministic query rules so results remain stable.
Course navigationPhase 15⌄
Filtering, Search, Sorting & Pagination
Implement scalable list endpoints with validated filtering/search/sorting/pagination and deterministic database queries.
Phase outcome and project increment
Learning outcome
Implement scalable list endpoints with validated filtering/search/sorting/pagination and deterministic database queries.
Concrete deliverable
Task list API with filter/search/sort/page contract plus metadata and indexes justified by measured query patterns.
Principles to keep while coding
Validate user-supplied sort fields instead of injecting arbitrary column names into queries.
Pagination metadata should match the strategy used by the API so clients can navigate correctly.
Indexes should follow real filter and sort patterns when performance data shows they are needed.
Guided phase lab
Do these in order. Each step should end with evidence before the next one starts.
- 01
Write the query contract including defaults and maximum page size.
- 02
Whitelist sortable fields and directions instead of interpolating arbitrary user input.
- 03
Implement filters and text search with clearly defined case/field semantics.
- 04
Start with offset pagination, then explain where cursor pagination changes consistency/performance.
- 05
Guarantee deterministic ordering with a unique tie-breaker.
- 06
Inspect representative query plans/index needs rather than indexing every field.
Failures you should know how to diagnose
Unbounded page size
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Nondeterministic order causing duplicates/missing rows
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Client sends database column names unchecked
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Counting/filtering with different predicates so metadata lies
Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.
Lessons
The source chapters come first. Every phase now ends with a guided build, a debugging workshop, and an independent challenge so reading turns into transferable skill.
Collection API Mental Model
Collection API Mental Model Treat filters, search, sorting and pagination as one public query contract Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend Arc
Query DTOs and Zod Validation
Query DTOs & Zod Validation Normalize strings, coerce numbers, apply defaults, cap page size, and reject unsupported parameters Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 -
Prisma Filtering and Where Design
Prisma Filtering & where Design Translate only approved public filters into Prisma query objects Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend Architect
Search Semantics and Case Insensitive Text
Search Semantics & Case-Insensitive Text Design simple search behavior while understanding its database trade-offs Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional
Safe Sorting and Stable Order
Safe Sorting & Stable Order Allow-list sort fields and add deterministic tie-breaking for pagination Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend Archi
Offset Pagination and Count Metadata
Offset Pagination & Count Metadata Implement numbered pages with skip/take and truthful totals Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend Architectur
Cursor Pagination and Strategy Selection
Cursor Pagination & Strategy Selection Understand feed-style pagination and choose it for the right product interaction Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Profess
Paginated Response Contract
Paginated Response Contract Return collection data with explicit navigation metadata instead of a bare array Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backe
Index Aware Query Design
Index-Aware Query Design Connect real query shapes to PostgreSQL indexes without over-indexing Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend Architectur
Next.js URL Query State
Next.js URL Query State Represent filters, sorting and page state in shareable URL search parameters Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend Archi
Hands On Lab and Phase Exit
Hands-On Lab & Phase Exit Build, test, reason about, and transfer a production-style collection endpoint Phase 15 - Filtering, Search, Sorting & Pagination Volume 04 - Professional Backend A
Guided Build Lab
Purpose Implement scalable list endpoints with validated filtering/search/sorting/pagination and deterministic database queries.
Debugging Workshop
Goal Learn to diagnose this phase's failures systematically instead of reinstalling packages, changing random configuration, or copying a fix without understanding it.
Phase Synthesis & Independent Challenge
Mission Rebuild the capability of Phase 15 without following the exact code line-by-line. The goal is transfer: can you use the ideas when the variable names, UI, or route shape changes?
Mastery Capstone Lab
Integrate Phase 15 without following a transcript.
Exercises that increase independence
Write the query contract including defaults and maximum page size.
Implement filters and text search with clearly defined case/field semantics.
reproduce and diagnose — Unbounded page size.
deliver Task list API with filter/search/sort/page contract plus metadata and indexes justified by measured query patterns.
Can design a stable list contract and Can validate sort/filter input.
Do not leave the phase only because you reached the bottom
Phase exit criteria
Scenario and concept quiz
These questions test engineering choices rather than whether you remember a lesson title.
Reference files
Attempt the work first. Use snapshots to compare a checkpoint, not as a substitute for understanding.
reference/TaskFlow_Collection_API_Snapshot/README.txt
TaskFlow Phase 15 reference snapshot This snapshot contains only the files changed or emphasized by Phase 15 and assumes Phase 14 is complete. Implemented collection behavior: - validated query parameters - project/status/priority/assignee filters - case-insensitive title/description substring search - allow-listed sorting - stable id tiebreaker - offset pagination with metadata - total-count query grouped with the page query - Next.js server-side query-state example Cursor pagination is taught conceptually but not used as the primary API contract because the current TaskFlow UI is page-number oriented.
reference/TaskFlow_Collection_API_Snapshot/apps/api/src/tasks/tasks-query.schemas.ts
import { z } from "zod";
import {
TaskPriority,
TaskStatus,
} from "../generated/prisma/client";
export const taskSortFieldSchema = z.enum([
"createdAt",
"updatedAt",
"dueAt",
"title",
]);
export const sortOrderSchema = z.enum(["asc", "desc"]);
export const taskListQuerySchema = z.strictObject({
projectId: z.uuid().optional(),
assigneeId: z.uuid().optional(),
status: z.enum(TaskStatus).optional(),
priority: z.enum(TaskPriority).optional(),reference/TaskFlow_Collection_API_Snapshot/apps/api/src/tasks/tasks.controller.ts
import {
Controller,
Get,
Param,
Query,
} from "@nestjs/common";
import { StandardSchemaValidationPipe } from "@nestjs/common";
import { TasksService } from "./tasks.service";
import {
taskIdSchema,
} from "./tasks.schemas";
import {
taskListQuerySchema,
TaskListQueryDto,
} from "./tasks-query.schemas";
@Controller("tasks")
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
reference/TaskFlow_Collection_API_Snapshot/apps/api/src/tasks/tasks.service.ts
import { Injectable } from "@nestjs/common";
import {
Prisma,
TaskStatus,
} from "../generated/prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import {
TaskNotFoundException,
} from "../common/errors/application.exceptions";
import {
TaskListQueryDto,
TaskSortField,
SortOrder,
} from "./tasks-query.schemas";
@Injectable()
export class TasksService {
constructor(private readonly prisma: PrismaService) {}
async findAll(query: TaskListQueryDto) {reference/TaskFlow_Collection_API_Snapshot/apps/web/src/app/tasks/page.tsx
import { fetchTasks } from "@/lib/api/tasks";
type SearchParams = Record<
string,
string | string[] | undefined
>;
type TasksPageProps = {
searchParams: Promise<SearchParams>;
};
function first(value: string | string[] | undefined) {
return Array.isArray(value) ? value[0] : value;
}
export default async function TasksPage({ searchParams }: TasksPageProps) {
const params = await searchParams;
const result = await fetchTasks({
status: first(params.status),reference/TaskFlow_Collection_API_Snapshot/apps/web/src/lib/api/tasks.ts
export type TaskListMeta = {
page: number;
pageSize: number;
totalItems: number;
totalPages: number;
hasPreviousPage: boolean;
hasNextPage: boolean;
};
export type TaskListResponse<TTask> = {
data: TTask[];
meta: TaskListMeta;
};
export type TaskListParams = {
projectId?: string;
assigneeId?: string;
status?: string;
priority?: string;
search?: string;reference/TaskFlow_Collection_API_Snapshot/database/index-notes.md
# Phase 15 index notes Do not add every conceivable index. Start from actual queries and EXPLAIN output. The existing TaskFlow model already has: ```prisma @@index([projectId, status]) @@index([assigneeId, status]) ``` A frequently measured project listing such as: - WHERE projectId = ? - ORDER BY createdAt DESC may justify an additional index such as:
reference/TaskFlow_Collection_API_Snapshot/examples/cursor-pagination.example.ts
// Concept-only example: cursor pagination needs a deliberately stable cursor strategy.
// TaskFlow implements offset pagination first because the current UI uses page numbers.
const page = await prisma.task.findMany({
take: 20,
cursor: cursorId ? { id: cursorId } : undefined,
skip: cursorId ? 1 : 0,
orderBy: { id: "asc" },
});
// Real production feeds often need a cursor aligned with the desired ordering.
// With random UUID ids and createdAt ordering, design the cursor intentionally
// instead of assuming offset and cursor pagination are interchangeable.reference/TaskFlow_Collection_API_Snapshot/tests/task-listing-smoke-tests.ps1
# Phase 15 collection API smoke checks.
$api = "http://127.0.0.1:4000"
# Defaults: page=1, pageSize=20, sort=createdAt, order=desc
Invoke-RestMethod -Uri "$api/tasks"
# Filter by status and priority.
Invoke-RestMethod -Uri "$api/tasks?status=IN_PROGRESS&priority=HIGH"
# Case-insensitive substring search.
Invoke-RestMethod -Uri "$api/tasks?search=payroll"
# Stable sorting with page metadata.
Invoke-RestMethod -Uri "$api/tasks?sort=title&order=asc&page=1&pageSize=10"
# Invalid page should fail validation.
try {
Invoke-RestMethod -Uri "$api/tasks?page=0"
} catch {
$_.ErrorDetails.Message