Nobody has started this yet — be first.
Business impact
Any client that tries to edit or delete a post/comment that's already gone -- because it was deleted moments ago in another tab, or the id was just typo'd or stale in a bookmark/cache -- gets a raw 500 Internal Server Error instead of a normal, expected 404. Every one of these also gets logged server-side via console.error, so this is also an on-call/logging problem: a completely routine, expected client mistake ("that post doesn't exist anymore") shows up in error monitoring looking exactly like a real server fault, drowning out signal that matters.
Problem
requireOwnership's ownershipCheck loads the resource, then checks resource.authorId !== req.user.id before checking whether resource is null. When the id doesn't correspond to any row, loader(id) resolves to null, and null.authorId throws a TypeError (Cannot read properties of null (reading 'authorId')). That error is caught by the surrounding try/catch, forwarded via next(err), and since it's a plain TypeError (not an AppError, and not a recognized Prisma error code), the centralized error handler's fallback branch turns it into a 500.
Current behavior
PUT /posts/:id, DELETE /posts/:id, and DELETE /comments/:id all return 500 (with a TypeError logged) for an id that doesn't exist, instead of a clean 404.
Expected behavior
PUT/DELETE on a post id (or DELETE on a comment id) that does not exist returns 404 NOT_FOUND -- exactly as the comments directly above each check in this file describe ("Genuinely doesn't exist: 404, not 403 -- do not conflate 'not yours' with 'not there.'"). Requests for a resource that does exist but belongs to someone else must still 403, unaffected by this fix.
Steps to reproduce
cd nodejs/blog_api npm test -- tests/ownership.test.js
curl -s -o /dev/null -w "%{http_code}\n" -X PUT http://localhost:3000/posts/999999
-H "Authorization: Bearer <token>" -H "Content-Type: application/json"
-d '{"title":"x"}'
Why this matters
This is an early-return/check-ordering mistake: the function needs its existence check to run strictly before anything that dereferences the loaded resource, because a missing resource and "resource owned by someone else" are two different, sequential questions ("does it exist?" then "is it yours?") -- answering the second question first, when the answer to the first is "no," crashes instead of answering "no."
Suggested approach
Look at the two if blocks in ownershipCheck in src/middleware/ownership.js right after const resource = await loader(id);. Each block's own comment describes which case it's for and in what order the function's doc-comment (top of the file) says they need to run -- check which one currently comes first against that stated order.
Acceptance criteria
Verification
node_modules/.bin/jest --config practice-tickets/jest.config.js practice-tickets/tests/ticket04_ownership_500_on_missing_resource.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/ownership-500-on-missing-resourceFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/ownership-500-on-missing-resourceSubmit 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.