Nobody has started this yet — be first.
Business impact
This project deliberately gives the expensive, abusable operation (POST /shorten -- a Postgres write plus a Redis write) a strict 10-requests/min limit, and the cheap, high-frequency operation (GET /:code -- a redirect real users click constantly) a loose 60-requests/min limit. Right now the two are swapped. The practical effect: a short link that gets shared somewhere popular -- a Slack channel, a group chat, anywhere multiple people click it from behind the same office/campus NAT within a minute -- starts throwing 429 Too Many Requests at real visitors after only 10 clicks from that shared IP, even though the whole point of a short link is to be clicked by lots of people. Meanwhile, the endpoint that actually costs a database write per request -- the one this rate limit exists to protect -- now allows 60 creates/min per IP instead of 10, six times the intended abuse budget. This is strictly worse than having no fix at all in either direction: legitimate traffic gets blocked, and the actually expensive endpoint gets weaker protection. Priority is P0 rather than a lower severity-sounding label because this is a security/abuse-control misconfiguration with real production impact in both directions at once, not a cosmetic bug.
Problem
POST /shorten is rate-limited using the limiter configured for redirects (60 requests/60s), and GET /:code is rate-limited using the limiter configured for creation (10 requests/60s) -- the two are wired to the wrong routes.
Current behavior
11 rapid POST /shorten requests from one IP all succeed (none hit the strict 10/min creation limit), while 11 rapid GET /:code requests against the same valid short code get a 429 after only 11 requests, well under the loose 60/min redirect limit.
Expected behavior
POST /shorten must be limited by SHORTEN_RATE_LIMIT_MAX / SHORTEN_RATE_LIMIT_WINDOW_MS (default: 10 requests / 60s). GET /:code must be limited by REDIRECT_RATE_LIMIT_MAX / REDIRECT_RATE_LIMIT_WINDOW_MS (default: 60 requests / 60s).
Steps to reproduce
for i in $(seq 1 11); do
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3003/shorten
-H 'Content-Type: application/json'
-d "{"longUrl":"https://example.com/$i\"}"
done
Why this matters
src/middleware/rateLimiter.js exports two independently-configured limiters, createLimiter (strict) and redirectLimiter (loose) -- that part is correct and doesn't need to change. The bug is entirely in which route file imports which one. Look at the require('../middleware/rateLimiter') line and the limiter passed as middleware in both src/routes/shorten.js's router.post('/shorten', ...) and src/routes/redirect.js's router.get('/:code', ...) -- cross-reference the name of the limiter each file imports against the name the README says each endpoint should use.
Suggested approach
Check both files side by side: src/routes/shorten.js and src/routes/redirect.js. Each imports one limiter from ../middleware/rateLimiter and passes it as middleware to its route. Verify which limiter name is bound to which route in each file, and compare that against what the limiter's own name says it's for (createLimiter vs. redirectLimiter).
Acceptance criteria
Verification
npx jest --config practice-tickets/jest.config.js practice-tickets/tests/04-rate-limiters-swapped.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 fix/rate-limiters-swappedFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/rate-limiters-swappedSubmit 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 1
Fix a bug
Read existing behaviour, correct it.