Cache it, rate limit it, and shed load before it buckles
Continues from the last build: The hot queries are indexed, joined properly and paginated by keyset; you have baselines.
One merchant's dashboard poller hits your aggregates endpoint fifty times a second.
What you'll build
You can absorb a runaway poller and a traffic spike without the database breaking a sweat, and you can prove the hit ratio and the p95 numbers with real commands. One noisy merchant now costs you a Redis lookup instead of a Postgres query, and a spike degrades predictably instead of catastrophically.
See how we teach, before you sign up
You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:
import Redis from 'ioredis';
export const redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379', {
maxRetriesPerRequest: 3,
});
redis.on('error', (err) => {
fastifyLoggerFallback('redis connection error', err);
});
function fastifyLoggerFallback(message: string, err: unknown) {
console.error(message, err);
}
Reading this file
new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379'Falls back to the local docker-compose Redis when REDIS_URL is unset.maxRetriesPerRequest: 3Bounds how long a single command retries before failing, so a Redis blip does not hang requests indefinitely.redis.on('error', (err) => {Logged rather than left to crash the process on a transient connection error.
One shared Redis client, reused by the cache-aside route, the invalidation helper, the rate limiter, and the load-shedding queue depth check.
That's 1 of 8 explained code blocks in this single project.
The build, milestone by milestone
- 1
Cache-aside the aggregates endpoint
6 guided stepsThe aggregates query is expensive and the poller calls it 50 times a second for numbers that change maybe once a minute. Every one of those calls that hits Postgres is wasted work competing with real writes for connections and buffer cache. A 60 second TTL means the worst case staleness is 60 seconds, which is fine for a dashboard, and it turns 50 queries a second into roughly one query a minute.
- 2
Bust the cache on every write that changes it
5 guided stepsA cache with only a TTL is a cache that lies for up to 60 seconds after every write. A merchant who just paid an order and refreshes their dashboard should see the new total immediately, not on a coin flip depending on when the TTL happens to expire. Explicit invalidation on write makes correctness the default and the TTL just a backstop for invalidation bugs.
- 3
Add ETag conditional GET on a read endpoint
6 guided stepsRedis caching helps your own server, but it does nothing for a CDN, a corporate proxy, or the client's own HTTP cache sitting in front of you. ETags let any standards-compliant cache, including ones you do not control, skip re-downloading a resource that has not changed, which cuts bandwidth and lets a 304 be served without your API doing any real work beyond a timestamp comparison.
- 4
Rate limit per API key with a sliding window in Redis
6 guided stepsAn in-memory counter (a plain JS Map keyed by API key) works fine with exactly one API instance and breaks the moment you run two. Each instance keeps its own count, so the real ceiling silently doubles per instance, and a restart resets everyone's counter to zero for free. Redis gives every instance a shared, authoritative view of how many requests a key has made recently, so the limit means the same thing no matter how many instances are running.
- 5
Shed load before Postgres melts
6 guided stepsRate limiting protects you from one misbehaving key, but a genuine traffic spike across many legitimate keys can still overwhelm the database. Without a saturation signal, every incoming request queues up behind an already-overloaded Postgres pool, latency climbs for everyone, and eventually requests start timing out instead of failing fast. A cheap, fast 503 with Retry-After costs almost nothing to check and lets well-behaved clients back off instead of piling on.
What's inside when you start
You'll walk away with
This is portfolio-grade. Build it free.
Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.
Start building