Fullstack CourseLearn by building
Back to week 4

Topic

Challenge: broken pagination / soft-delete leak

Definition

A soft-delete leak occurs when a query intended to exclude logically deleted rows omits the deletion filter, causing removed records to still appear in lists, counts, or pagination totals.

In simpler words

Deleted tickets are not really gone from the database. If a query forgets to filter them out, they reappear in lists and mess up page counts.

Combines pagination correctness with a realistic soft-delete pitfall.

After this you can

  • Explain why soft delete requires filtering in every read query, not only the list
  • Spot a pagination total that disagrees with the visible rows
  • Choose a fix that keeps the data query and the count query consistent

Why soft-delete leaks are easy to introduce

Definition

Soft delete marks a row as removed, for example with a deletion timestamp, without a physical delete, so every subsequent read query must explicitly exclude it, unlike a hard delete which removes the row from all future query results automatically.

In simpler words

Marking a row deleted does not stop it from showing up unless every query remembers to check for that mark.

A new query, index, or export path added later can easily forget the exclusion condition older queries already had. The leak often appears in only one endpoint, not all of them.

When the count query and the data query use different filters, the pagination total disagrees with what a user actually sees, producing an empty last page or missing rows.

Challenge lab

Bug report

Deleted tickets still show up on later pages of the tickets list, and the last page is sometimes empty.

What is broken

The paginated data query filters out soft-deleted tickets, but the separate count query used for the pagination total does not, so the total overcounts and the page math misaligns.

Broken snippet

async list(page: number, limit: number) {
  const [data] = await this.ticketsRepo.findAndCount({
    where: { deletedAt: null },
    skip: (page - 1) * limit,
    take: limit,
  });
  const total = await this.ticketsRepo.count(); // forgot the deletedAt filter
  return { data, meta: { page, limit, total } };
}

Pass criteria

  • Soft-deleted tickets never appear in list results
  • The pagination total matches the same filter used for the actual page of data
  • The pagination page count is consistent with what a user can actually page through

Choose the correct fix

Keep in mind

  • Every read query on a soft-deletable entity needs the same exclusion filter; factor it into one shared place.
  • A pagination total and the data query must share filters or pagination math breaks.
  • A leak that shows up only on later pages is a strong hint the count and data queries disagree.

Test

Check your understanding

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

Concept1. Which statement best defines the pagination and soft-delete challenge?
Syntax2. Which code-level choice matches the pagination and soft-delete challenge?
Practical3. A reviewer spots a bug related to the pagination and soft-delete challenge. What is the right fix?
Logic4. Which reasoning correctly explains the pagination and soft-delete challenge?
Concept5. Which boundary does correct use of the pagination and soft-delete challenge preserve?
Practical6. What is the safest next step when applying the pagination and soft-delete challenge?
Syntax7. Which implementation direction matches the rule for the pagination and soft-delete challenge?
Logic8. Which consequence follows from applying the pagination and soft-delete challenge correctly?
Concept9. Which claim about the pagination and soft-delete challenge is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates the pagination and soft-delete challenge?