BE10: Capstone: three backend projects for TiffinBox
Project 2: a fast, fresh menu
🎯You'll be able to
Make a slow, popular read route fast with one query, an index and a cache, without ever showing an old price after a change.
🏠The everyday picture
A busy café writes the day's menu on a board instead of reading the recipe book to every customer. When the price of chai changes, they rub it out and rewrite it straight away, and if the board falls down, they read from the book until it's back up.
📘What it really is
- In the starter's
api/menu.js, fix: - the N+1 queries: load dishes, ratings and 30-day order counts in one or two queries (BE3 Lesson 3);
- an index that supports the query, added as
db/migrations/002_...sql(BE3 Lesson 2); - a Redis cache with a key like
menu:v1:kitchen:<id>, a time-to-live, and one database load when many requests miss at once (BE4 Lessons 2 and 4); - clearing the cached menu when a dish changes, so the very next request is fresh (BE4 Lesson 3);
-
a fallback to the database when Redis is down, with short timeouts (BE4 Lesson 5, BE8 Lesson 3).
-
Record the load-test numbers before and after, with the API started with
RATE_LIMIT=1000000so the rate limit doesn't refuse the test (BE9 Lesson 5).
📝A worked example
Measured in our test environment on 1 November 2026 (a 4-core practice machine; your numbers will differ), using
npm run load -- http://localhost:4000/v1/kitchens/7/menu 300 10:
| Version | Database queries per cold request | Requests per second | p95 |
|---|---|---|---|
| Starter | 62 | about 7 | about 1.5 s |
| Finished | 2, then 0 while cached | about 1,000 | about 12 to 14 ms |
The finished version also answers correctly with Redis stopped (X-Cache: BYPASS), within the 1.5 s the test allows.
🌍Where it's used
- Product and menu pages on shopping and delivery apps.
- News and sports scores, cached with short lifetimes.
- Any read-heavy API where most requests ask for the same thing.
📝Case scenario
A learner's cache made the menu fast, but test 7 failed: after a price change, the next request still showed the old price. They had set a 5-minute time-to-live and expected that to be enough. Deleting the key right after the database update, in the change route, fixed it, and the time-to-live stayed as a safety net.
🖱️Try it
Fix api/menu.js and add your index migration until npm run test:cache shows 9 of 9 passing. Record the load test before and after.
⚠️Common mistakes
- Caching before fixing the query. Sign: cold requests and stampedes are still slow. Fix: fix the N+1 queries first, then cache.
- Relying on expiry for freshness. Sign: old prices after a change. Fix: delete the key when the data changes.
🛠️For your project
Project 2: fix api/menu.js and add db/migrations/002_...sql until the 9 tests in tests/cache.test.js pass.
✅Quick check
Why fix the N+1 queries before adding a cache?
Show the answer
Cold requests and cache misses still run the query; a slow query makes misses and stampedes slow.
How does the menu stay fresh after a price change?
Show the answer
The change route deletes the cached key right after the database update, so the next request loads fresh data.
