Skip to content
Caching

Lesson 14 of 14 · 40 min

x
14/14

Lesson position in the course — not completion. Use Mark Complete to track finished lessons (saved in this browser).

Final Project: Build a Cached URL Shortener API

Put everything together in a URL shortener API that demonstrates production-grade caching. The read path (resolve a short code to a URL) should be extremely fast — this is 100x more frequent than the write path (create a short URL).

Implement cache-aside with Redis as L2 and an in-process LRU as L1. Set TTL on cached URLs (1 hour) plus event-based invalidation when a URL is updated or deleted. Add jittered TTLs to prevent thundering herd on popular links. Use HTTP Cache-Control headers on the redirect response for CDN and browser caching of stable URLs.

Benchmark with k6 or a simple load test script: run 1000 concurrent requests against /:code with an empty cache (cold start), then with a warm cache. You should see 10–50x latency improvement and near-zero database queries at high hit ratios. Track hit ratio, p99 latency, and Redis memory usage throughout.

Before
Uncached — every redirect hits the database
1app.get('/:code', async (req, res) => {2  const url = await db.query(3    'SELECT long_url FROM urls WHERE code = $1',4    [req.params.code]5  );6  if (!url) return res.status(404).end();7  res.redirect(302, url.long_url);8});9// 10k redirects/sec = 10k DB queries/sec
After
Full caching stack — L1 + L2 + HTTP headers
1const l1 = new LRUCache({ max: 1000 });2 3app.get('/:code', async (req, res) => {4  const { code } = req.params;5 6  // L1 in-process7  const local = l1.get(code);8  if (local) {9    res.set('Cache-Control', 'public, max-age=300');10    return res.redirect(302, local);11  }12 13  // L2 Redis14  const cached = await redis.get(`url:${code}`);15  if (cached) {16    l1.set(code, cached);17    res.set('Cache-Control', 'public, max-age=300');18    return res.redirect(302, cached);19  }20 21  // Database (cache miss)22  const row = await db.query(23    'SELECT long_url FROM urls WHERE code = $1', [code]24  );25  if (!row) return res.status(404).end();26 27  const ttl = 2700 + Math.random() * 600; // jittered 45–55 min28  await redis.setex(`url:${code}`, ttl, row.long_url);29  l1.set(code, row.long_url);30  res.set('Cache-Control', 'public, max-age=300');31  res.redirect(302, row.long_url);32});

Exercise

Build (or sketch in one repo folder) a short-code redirect path with L1 LRU + Redis L2, jittered TTL, and Cache-Control on redirects. Load-test cold vs warm and record p50/p99 plus DB query count. No starter repo is bundled — use your own stack.

Previous

Progress is saved in this browser.

Finish Course