Stop 17 · System Design for Full-Stack AppsTopics · 3/7

Caching Strategies

Part of the Full Stack Developer Roadmap

In short

The fastest request is the one that never reaches your database. A full stack app can cache in four or five places, and knowing which layer answered is half of debugging stale data.

Deciding what to cache and where — browser cache, CDN, server-side cache (Redis), and client-side query cache (React Query) — across your whole request path.

The purple blob pulls a book from a school bag next to a small bookshelf, with a library far behind.

Think of it like this

Bag, room shelf, library

Need a textbook? First you check your bag. If it's not there, the shelf in your hostel room. Then the college library. Only if the library lacks it do you order from the publisher. Each stop is slower, so you keep favourites close.

Watch it happen

Where does GET /api/products/7 get answered?

Browser
React Query
CDN
Redis
Database

1/5The browser checks its HTTP cache. With max-age=60 and a fresh copy, nothing leaves your phone.

The big ideas

What you need to know

Every cache needs an expiry

Pick a TTL per layer. Short for prices and stock, long for images and blog posts.

Plan how to clear it

When data changes, delete the Redis key, invalidate the React Query key, and purge the CDN if needed.

Never share private data

Personal responses must be Cache-Control: private or no-store, or a CDN may serve your cart to a stranger.

Cache close to the user

A hit in the browser beats a hit in Redis, which beats any database query. Cache as high up as is safe.

See it in code

Walk through the code

ts
1app.get("/api/products/:id", async (req, res) => {2  const key = `product:${req.params.id}`;3  const cached = await redis.get(key);4  if (cached) {5    res.set("Cache-Control", "public, max-age=60, s-maxage=300");6    return res.type("json").send(cached);7  }8 9  const product = await db.product.findUnique({ where: { id: req.params.id } });10  if (!product) return res.status(404).json({ error: "Not found" });11  await redis.set(key, JSON.stringify(product), { EX: 300 });12  res.set("Cache-Control", "public, max-age=60, s-maxage=300");13  res.json(product);14});15 16// When an admin edits the product: await redis.del(`product:${id}`);

1/4Look in Redis first. On a hit, send the stored JSON without touching the database.

Cache-aside with Redis on the server, plus Cache-Control so browsers and the CDN can cache too. One route, three cache layers.

Save yourself the pain

Common mistakes

Oops

Adding a cache and never deciding how it gets cleared.

Do this instead

Write the invalidation code in the same pull request: what event clears which key, at which layer.

Oops

Putting Cache-Control: public on /api/me or /api/cart.

Do this instead

Use private or no-store for anything tied to a logged-in user.

Your mission

Find the layer that answered

~35 min

Quick check

You changed a product price, the database shows ₹499, but users still see ₹599 for a few minutes. Most likely cause?

Go deeper

  1. 1Map every place your full-stack app currently caches data, if any.
  2. 2Add one deliberate caching layer to a slow, frequently-requested piece of data.
  3. 3Design your invalidation strategy alongside the cache itself, not as an afterthought.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge