Nobody has started this yet — be first.
Business impact
Anyone browsing posts loses the newest content. GET /posts (and GET /posts/:id/comments) always renders newest-first, so "page 1" is supposed to be the freshest, most relevant page -- instead it silently skips exactly limit records before returning anything. On a blog with fewer posts than the page size, the homepage just renders empty. On a busier one, users land on page 1 and see content that's already one page stale. This is the kind of bug that looks like "the site is broken" to a reader and generates support/bug reports with no obvious cause, since nothing errors -- it just quietly returns the wrong slice.
Problem
toSkipTake(page, limit) computes skip = page * limit. For page=1, that's skip = limit -- i.e. the first page skips the first limit records instead of starting at offset 0.
Current behavior
GET /posts?page=1&limit=N returns 0 posts (or an already-stale window) even though N or more posts exist, because the very first page is computed as if it were the second.
Expected behavior
page=1 must return records starting at offset 0 (the newest records, given the orderBy: { createdAt: 'desc' } on GET /posts) -- it must never skip the first record. Later pages, and the "no error on the last partial page" and "empty array past the last page" behaviors, should all still work exactly as they do today.
Steps to reproduce
cd nodejs/blog_api npm test -- tests/pagination.test.js
node -e "console.log(require('./src/lib/pagination').toSkipTake(1, 10))"
Why this matters
This is a one-line off-by-one in the page->offset translation, the kind of mistake that's easy to make when writing this formula from memory (page * limit "feels" right at a glance) and easy to miss in review because it still type-checks and still returns a page of results -- just never the right one for page=1, and consistently the wrong window for every other page too.
Suggested approach
Look at toSkipTake in src/lib/pagination.js. The file's own comment block states the contract explicitly ("page=1 must return records starting at offset 0"); work out what skip needs to be when page=1 under that contract, and generalize from there.
Acceptance criteria
Verification
node_modules/.bin/jest --config practice-tickets/jest.config.js practice-tickets/tests/ticket01_pagination_skip_off_by_one.test.js --verbose
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
$git checkout -b fix/pagination-skip-off-by-oneFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/pagination-skip-off-by-oneSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign indocker compose down -vTo run locally without Docker, you need a local PostgreSQL instance:
npm install
cp .env.example .env # edit DATABASE_URL, JWT_SECRET, etc.
npm run prisma:migrate:dev # applies migrations to your local Postgres
npm run dev # starts with --watch on http://localhost:3000
Run the project's own test suite (real Postgres, not mocks -- point DATABASE_URL at a disposable database first):
npm test
Work the tickets in practice-tickets/ (ticket01 through ticket07); each has its own dedicated Jest test under practice-tickets/tests/, run via a separate Jest config (practice-tickets/jest.config.js) so plain npm test never picks them up:
docker compose up -d postgres # if it isn't already running
npm run prisma:migrate
./practice-tickets/run_tickets.sh # all 7, clean pass/fail summary
node_modules/.bin/jest --config practice-tickets/jest.config.js \
practice-tickets/tests/ticket01_pagination_skip_off_by_one.test.js --verbose # a single ticket
Level 1
Fix a bug
Read existing behaviour, correct it.