User Reading Shelf API
Introduction: Why Track Reading Progress?
Welcome back! In the previous lesson, you learned how to build a book catalog and search for books in your reading tracker API. Now, let’s take the next step: tracking your own reading progress and organizing your personal shelf.
Imagine you want to see which books you’ve finished, which ones you’re still reading, and which you want to read next. This is where per-user reading status and the shelf API come in. By the end of this lesson, you’ll understand how to store and update your reading progress and how to view your shelf with filters and sorting options.
How Per-User Reading Status Works
Each user can have their own reading progress for each book. This is stored in the ReadingSession object. The key fields are:
userId: Whose progress is this?bookId: Which book?currentPage: What page is the user on?status: What is the reading status? (optional, but useful)
The status can be one of:
"not-started""in-progress""completed""want-to-read"
Let’s look at how a reading session is updated in the service:
What it does
Upserts a user’s reading session for a given book and normalizes status. The method guarantees the user/book exist, derives status when not explicitly provided, and stamps updatedAt so the client can order shelf activity.
Execution flow
- Existence checks —
usersService.findOne(dto.userId)andbooksService.findOne(dto.bookId)throw if the user/book doesn’t exist, preventing orphan sessions. - Normalization
- If
dto.status === 'want-to-read', the method forces{ currentPage: 0, status: 'want-to-read' }, ignoring any incomingcurrentPage. This models intent (“queue this book”) and prevents phantom progress. - Otherwise, it sets
currentPage = dto.currentPageandstatus = dto.status ?? deriveStatus(currentPage, totalPages). deriveStatusrules:currentPage <= 0 → 'not-started';currentPage >= totalPages (with totalPages > 0) → 'completed'; else'in-progress'.
- If
- Upsert — Looks up an existing session by
(userId, bookId).- If found, it mutates
currentPage,status, andupdatedAt. - If absent, it pushes a new session with those fields plus
userId,bookId.
- If found, it mutates
- Return — Returns the persisted session object (useful for immediate UI reconciliation).
Explanation:
- The method checks if a reading session already exists for the user and book.
- If it does, it updates the progress and status.
- If not, it creates a new session.
- The status is set based on the current page and total pages, or directly from the user’s input.
Example Output:
Suppose Alice (userId: 2) updates her progress on "The Hobbit" (bookId: 1) to page 50:
