Day 2 of 5
⏱ ~60 minutes
Redis in 5 Days — Day 2

Caching Patterns

Cache database query results, set TTL expiration, implement cache invalidation, and measure hit rates.

The Cache-Aside Pattern

Also called lazy loading. Check the cache first. If found (cache hit), return it. If not (cache miss), fetch from the database, store in cache, return to caller.

Node.js cache-aside
import { createClient } from 'redis';
const redis = createClient();
await redis.connect();

async function getUser(id) {
  const cacheKey = `user:${id}`;

  // 1. Check cache
  const cached = await redis.get(cacheKey);
  if (cached) {
    console.log('Cache HIT');
    return JSON.parse(cached);
  }

  // 2. Cache miss → query database
  console.log('Cache MISS');
  const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);

  // 3. Store in cache with TTL
  await redis.setEx(cacheKey, 3600, JSON.stringify(user));

  return user;
}

// Invalidate cache when user is updated
async function updateUser(id, data) {
  await db.query('UPDATE users SET ...');
  await redis.del(`user:${id}`);  // invalidate
}
Write-through pattern
// Write to cache AND database together
async function createPost(data) {
  const post = await db.create(data);
  const cacheKey = `post:${post.id}`;
  await redis.setEx(cacheKey, 1800, JSON.stringify(post));
  // Also invalidate the list cache
  await redis.del('posts:all');
  return post;
}
💡
Cache keys should be namespaced and predictable: resource:id or resource:filter:value. Example: user:123, posts:page:2, products:category:electronics. This makes bulk invalidation possible with SCAN.
📝 Day 2 Exercise
Add Caching to an API
  1. A
  2. d
  3. d
  4. R
  5. e
  6. d
  7. i
  8. s
  9. c
  10. a
  11. c
  12. h
  13. i
  14. n
  15. g
  16. t
  17. o
  18. a
  19. n
  20. E
  21. x
  22. p
  23. r
  24. e
  25. s
  26. s
  27. r
  28. o
  29. u
  30. t
  31. e
  32. .
  33. C
  34. h
  35. e
  36. c
  37. k
  38. c
  39. a
  40. c
  41. h
  42. e
  43. f
  44. i
  45. r
  46. s
  47. t
  48. .
  49. O
  50. n
  51. m
  52. i
  53. s
  54. s
  55. ,
  56. f
  57. e
  58. t
  59. c
  60. h
  61. f
  62. r
  63. o
  64. m
  65. y
  66. o
  67. u
  68. r
  69. d
  70. a
  71. t
  72. a
  73. b
  74. a
  75. s
  76. e
  77. ,
  78. c
  79. a
  80. c
  81. h
  82. e
  83. t
  84. h
  85. e
  86. r
  87. e
  88. s
  89. u
  90. l
  91. t
  92. w
  93. i
  94. t
  95. h
  96. 1
  97. 0
  98. -
  99. m
  100. i
  101. n
  102. u
  103. t
  104. e
  105. T
  106. T
  107. L
  108. .
  109. A
  110. d
  111. d
  112. a
  113. D
  114. E
  115. L
  116. E
  117. T
  118. E
  119. e
  120. n
  121. d
  122. p
  123. o
  124. i
  125. n
  126. t
  127. t
  128. h
  129. a
  130. t
  131. i
  132. n
  133. v
  134. a
  135. l
  136. i
  137. d
  138. a
  139. t
  140. e
  141. s
  142. t
  143. h
  144. e
  145. c
  146. a
  147. c
  148. h
  149. e
  150. e
  151. n
  152. t
  153. r
  154. y
  155. .

Day 2 Summary

  • Cache-aside: check cache → miss → fetch DB → store in cache. Most common pattern.
  • Always set a TTL. Caches without expiration become stale data graveyard.
  • Invalidate the cache entry when the underlying data changes.
  • Namespaced keys: user:123, posts:page:2. Consistent naming enables bulk operations.
Finished this lesson?