Course navigationPhase 15
Volume 04 · Phase 15

Filtering, Search, Sorting & Pagination

Implement scalable list endpoints with validated filtering/search/sorting/pagination and deterministic database queries.

15lessons + labs
5klesson words
9source references
What you will ship

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.

Figure 15.1 — Where this phase lives in TaskFlow
URL query state
validated query object
Prisma where/order/cursor
PostgreSQL plan
page response
frontend navigation
Trace the flow left-to-right for implementation and right-to-left for debugging. At every arrow, ask what data crosses the boundary and what proves it is trustworthy.
Instructor mental model

Principles to keep while coding

01

Filtering, sorting, and pagination must use deterministic query rules so results remain stable.

02

Validate user-supplied sort fields instead of injecting arbitrary column names into queries.

03

Pagination metadata should match the strategy used by the API so clients can navigate correctly.

04

Indexes should follow real filter and sort patterns when performance data shows they are needed.

Code-along path

Guided phase lab

Do these in order. Each step should end with evidence before the next one starts.

  1. 01

    Write the query contract including defaults and maximum page size.

  2. 02

    Whitelist sortable fields and directions instead of interpolating arbitrary user input.

  3. 03

    Implement filters and text search with clearly defined case/field semantics.

  4. 04

    Start with offset pagination, then explain where cursor pagination changes consistency/performance.

  5. 05

    Guarantee deterministic ordering with a unique tie-breaker.

  6. 06

    Inspect representative query plans/index needs rather than indexing every field.

Debugging studio

Failures you should know how to diagnose

Failure drill 1

Unbounded page size

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 2

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.

Failure drill 3

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.

Failure drill 4

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.

Watch-order style

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.

01

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

2 min
02

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 -

2 min
03

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

2 min
04

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

2 min
05

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

2 min
06

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

2 min
07

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

2 min
08

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

2 min
09

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

2 min
10

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

2 min
11

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

3 min
LAB

Guided Build Lab

Purpose Implement scalable list endpoints with validated filtering/search/sorting/pagination and deterministic database queries.

3 min
LAB

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.

2 min
LAB

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?

2 min
LAB

Mastery Capstone Lab

Integrate Phase 15 without following a transcript.

2 min
Practice ladder

Exercises that increase independence

Guided

Write the query contract including defaults and maximum page size.

Practice

Implement filters and text search with clearly defined case/field semantics.

Debug

reproduce and diagnose — Unbounded page size.

Independent

deliver Task list API with filter/search/sort/page contract plus metadata and indexes justified by measured query patterns.

Explain-back

Can design a stable list contract and Can validate sort/filter input.

Mastery gate

Do not leave the phase only because you reached the bottom

Phase exit criteria

Knowledge check

Scenario and concept quiz

These questions test engineering choices rather than whether you remember a lesson title.

0/0 correct · 0/8 answered
01Which practice best matches Phase 15's engineering goal?
02Which practice best matches Phase 15's engineering goal?
03Which practice best matches Phase 15's engineering goal?
04Which practice best matches Phase 15's engineering goal?
05You are working through the Phase 15 lab. Which next action is the most disciplined step?
06You are working through the Phase 15 lab. Which next action is the most disciplined step?
07You are working through the Phase 15 lab. Which next action is the most disciplined step?
08You are working through the Phase 15 lab. Which next action is the most disciplined step?
Source snapshot

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