Nobody has started this yet — be first.
Business impact
Today the only way to find out whether a customSlug like "launch-2026" is already taken is to submit the full POST /shorten request and see if it comes back 409. For a client building a "claim your custom link" UI with live feedback as the user types, that means firing a real write attempt just to answer a yes/no question, and it means every "just checking" keystroke burns a slot out of the strict POST /shorten rate limit (10/min) -- a user experimenting with a few slug ideas could lock themselves out of actually creating their link. A cheap, read-only availability check fixes both problems.
Problem
GET /shorten/available/:slug is registered as a stub in src/routes/shorten.js (right before module.exports). It compiles and the route exists, but its handler unconditionally throws -- hitting it always returns a 500 with a generic "Internal server error" body, regardless of :slug.
Current behavior
GET /shorten/available/any-slug always returns HTTP 500, never a real available: true/false answer.
Expected behavior
GET /shorten/available/:slug validates the path parameter using the same slug format rules POST /shorten already enforces for customSlug (3–32 chars, [a-zA-Z0-9_-]+). A malformed slug returns 400 with a validation error body in the same shape the existing errorHandler already produces for ZodErrors. For a well-formed slug, it returns 200 { "available": true } if no Link currently has that shortCode, or 200 { "available": false } if one does. It is strictly read-only -- it never creates, modifies, or reserves anything, and makes no promise the slug will still be free by the time a real POST /shorten for it lands (that race is what the existing 409 collision handling on POST /shorten is already for).
Steps to reproduce
npm run dev curl -s http://localhost:3003/shorten/available/brand-new-slug
Why this matters
There's no new concept here -- this is a read path that reuses two things that already exist in this codebase: the customSlug Zod schema (for format validation) and findByShortCode (for the existence check, src/lib/linkService.js). The main thing to get right is reusing them rather than re-deriving your own regex or your own Prisma query -- if your slug-format rules drift even slightly from what POST /shorten actually enforces, this endpoint could tell a client "available" for a slug that POST /shorten would then reject as malformed, or vice versa.
Suggested approach
Look at customSlug in src/lib/validation.js -- it's already exported indirectly as part of shortenRequestSchema, but you can also parse a bare string against a Zod schema on its own (someSchema.parse(value), throwing a ZodError on failure -- which the existing errorHandler already knows how to turn into a 400, the same way every other route in this file handles validation). For the existence check, findByShortCode in src/lib/linkService.js already does exactly the lookup you need -- you just have to decide what "found" vs. "not found" means for available.
Acceptance criteria
Verification
npx jest --config practice-tickets/jest.config.js practice-tickets/tests/02-slug-availability-check.test.js
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 feat/slug-availability-checkFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/slug-availability-checkSubmit 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 inOr run the whole stack in Docker instead of the steps above -- docker compose up --build (dev, hot-reloaded via a bind-mounted src/) or docker compose -f docker-compose.prod.yml up --build (prod-style multi-stage build, non-root user). Either way the app container runs prisma migrate deploy on startup, so no separate migration step is needed. Host ports are non-default -- app 3003, Postgres 5436, Redis 6380 -- to avoid clashing with sibling projects in this repo.
The project's own real test suite (npm test, Jest + Supertest against real Postgres/Redis) is separate from the practice tickets below -- run it any time to confirm you haven't broken anything already-working.
Each ticket in practice-tickets/tickets/ (01 through 07) names a dedicated test under practice-tickets/tests/, run via its own Jest project (practice-tickets/jest.config.js, excluded from plain npm test):
# a single ticket
npx jest --config practice-tickets/jest.config.js practice-tickets/tests/01-health-check-false-positive.test.js
# all 7, one at a time, with a pass/fail summary
./practice-tickets/run.sh
Fixing tickets 01, 03, 04, and 07 also turns several pre-existing failures in the real suite (tests/health.test.js, tests/redirect.test.js, tests/concurrency.test.js) back to green -- that's expected, not a coincidence, since the injected bugs live in shared code that suite also exercises. tests/concurrency.test.js specifically fails for two unrelated reasons at once (tickets 04 and 07 both touch code paths it exercises), so fixing only one of the two will not turn it green.
Level 2
Implement a feature
Extend the system within its own patterns.