4 min read

Reducing API Response Time by 90% with Redis Caching

Slow APIs frustrate users and overload databases. In many applications, the same data is requested again and again, such as product lists, user profiles, or course catalogs. In this article, we add Re...

#Data Analyst#Technical Guide
DBERT Verified Contributor Dossier

Maroju Prince Karthik

Data Analyst Fellow • Data Analyst

₹5,000 Production Milestone Disbursed

Want to build production projects like Maroju Prince Karthik?

Work 1:1 with practicing Delhi NCR engineers, build verified GitHub codebases, and earn production stipends up to ₹5,000.

Introduction

Slow APIs frustrate users and overload databases. In many applications, the same data is requested again and again, such as product lists, user profiles, or course catalogs. In this article, we add Redis caching to a Node.js API and measure the difference.

The Problem

Consider an endpoint that returns the top 20 products:

JAVASCRIPT
app.get("/products/top", async (req, res) => {
  const products = await db.query(
    "SELECT * FROM products ORDER BY sales DESC LIMIT 20"
  );
  res.json(products.rows);
});

Every request hits the database. Under heavy traffic, the query runs thousands of times per minute even though the data changes only occasionally. In our test, this endpoint averaged ~480 ms per request.

What Is Redis?

Redis is an in-memory key-value store. Because data lives in RAM, reads typically take under a millisecond. It is commonly used for caching, session storage, and rate limiting.

The Cache-Aside Pattern

We use the cache-aside (lazy loading) pattern:

  1. Check Redis for the data.
  2. Cache hit: return it immediately.
  3. Cache miss: query the database, store the result in Redis with an expiry, then return it.
CODE
Request -> Redis -> (hit) -> Response
              |
           (miss)
              v
          Database -> store in Redis -> Response

Step 1: Set Up Redis

Run Redis with Docker:

BASH
docker run -d --name redis -p 6379:6379 redis:7-alpine

Install the client:

BASH
npm install redis

Step 2: Connect to Redis

CODE
const { createClient } = require("redis");

const redis = createClient({ url: "redis://localhost:6379" }); redis.on("error", (err) => console.error("Redis error:", err));

(async () => { await redis.connect(); })(); ```

Step 3: Add Caching to the Endpoint

CODE
app.get("/products/top", async (req, res) => {
  const cacheKey = "products:top20";

try { const cached = await redis.get(cacheKey); if (cached) { return res.json(JSON.parse(cached)); } } catch (err) { console.error("Cache read failed:", err); }

const result = await db.query( "SELECT * FROM products ORDER BY sales DESC LIMIT 20" );

try { await redis.set(cacheKey, JSON.stringify(result.rows), { EX: 60 }); } catch (err) { console.error("Cache write failed:", err); }

res.json(result.rows); }); ```

Key details:

  • EX: 60 sets a 60-second TTL, so stale data expires automatically.
  • Cache errors are caught so a Redis outage never breaks the API. It simply falls back to the database.

Step 4: Invalidate the Cache on Updates

When data changes, delete the cached copy so users don't see outdated results:

CODE
app.put("/products/:id", async (req, res) => {
  await db.query("UPDATE products SET name = $1 WHERE id = $2", [
    req.body.name,
    req.params.id,
  ]);

await redis.del("products:top20"); res.sendStatus(204); }); ```

Results

We load-tested the endpoint with 1,000 requests:

MetricWithout CacheWith Redis
Avg response time~480 ms~12 ms
DB queries1,00017
Requests/sec~210~2,800

Response time dropped by roughly 97%, and database load fell almost entirely. Your numbers will vary with data size and hardware.

Best Practices

  1. Always set a TTL so memory doesn't fill with stale keys.
  2. Use clear key names, such as products:top20 or user:42:profile.
  3. Cache only read-heavy, slow-changing data.
  4. Prevent cache stampedes. When a popular key expires, many requests may hit the database at once. Use locking or slightly randomized TTLs.
  5. Monitor hit rate. A low hit rate means the cache isn't helping.
  6. Never cache sensitive per-user data under shared keys.

Common Pitfalls

  • Forgetting invalidation, which leads to users seeing old data.
  • Caching data that changes every second.
  • Treating Redis as the source of truth. It is a cache, and the database remains authoritative.
  • Letting a Redis failure crash the API instead of falling back gracefully.

Conclusion

Adding Redis with the cache-aside pattern takes only a few lines of code but delivers a large performance gain. Start with your slowest, most-read endpoints, set sensible TTLs, invalidate on writes, and measure the results. Caching is one of the highest-impact optimizations available to backend developers.

DBERT Verified Contributor Dossier

Maroju Prince Karthik

Data Analyst Fellow • Data Analyst

₹5,000 Production Milestone Disbursed

Want to build production projects like Maroju Prince Karthik?

Work 1:1 with practicing Delhi NCR engineers, build verified GitHub codebases, and earn production stipends up to ₹5,000.

🛡️ DBERT Institutional Quality Guarantee • Academic Peer Review

This technical publication represents an authenticated production milestone engineered during the DBERT Incubation Fellowship. The underlying codebase, automated test suites, and system architecture have undergone independent rubric evaluation with Turnitin originality validation (< 3.0% similarity index).

For Aspiring Engineers

Build Production Systems

Join the next cohort, receive 1:1 mentorship from industry staff, and publish verified capstone case studies.

Apply for Fellowship →
For Tech Recruiters & Employers

Hire Pre-Vetted Fellows

Discover engineers with verified code rubrics, live deployment URLs, and zero recruiter markup fees.

Browse Pre-Vetted Directory →