Speed Up Your API with Redis Caching

Build a weather API that uses Redis caching to cut response times by 100x.

Introduction

30 Second Summary

Every time you use an app that loads instantly, there is a good chance caching is doing the heavy lifting behind the scenes. Instead of fetching the same data over and over from a slow source, a cache stores recent answers so the next request gets served in milliseconds.

In this project, you will build a weather API that caches external API responses in Redis, turning requests that take 200-500ms into responses that arrive in 1-5ms.

What You'll Build

A weather API server that fetches forecast data from Open-Meteo and caches responses in Redis using the cache-aside pattern.

By the end of this project, you'll have:

  • An Express API with cached and uncached weather endpoints for side-by-side performance comparison.
  • A Redis-backed cache with configurable TTL expiration that automatically refreshes stale data.
  • Custom response headers showing cache HIT/MISS status and response timing for full observability.
  • Secret Mission: cache invalidation and statistics endpoints to manage your cache like a pro.

Are there any prerequisites?

You should have basic experience building routes and middleware with Express.js. You do not need any prior experience with Redis or caching.

Before We Start

Every time your API calls an external service, your users wait. If 100 users request the same weather data in a minute, that's 100 identical network round-trips to a server you don't control.

In this step, you'll install Redis (an in-memory data store perfect for caching), create your project folder, and install the packages you need to build your weather API.

In this step, get ready to:
  • Install Redis and start it as a background service.
  • Create the project folder and set up package.json.
  • Verify Node.js, Redis, and all dependencies are working.
Install Redis via Homebrew

Redis is an in-memory key-value store that applications use as a cache layer. Because it keeps data in RAM instead of on disk, reads are incredibly fast.

  • Open your terminal by pressing Cmd+Space and typing Terminal, then pressing Enter.
  • Install Redis by running this command:
brew install redis

What does this command do?

Homebrew downloads and installs the Redis server and its command-line tools (redis-server and redis-cli) onto your Mac. This gives you a local Redis instance you can start and stop whenever you need it.

  • Start Redis as a background service by running:
brew services start redis

Why a background service?

Running Redis via brew services means it starts automatically and keeps running even after you close this terminal window. You won't need to manually start it each time you work on this project.

  • Verify Redis is running by opening the Redis CLI and sending a ping:
redis-cli ping

You should see PONG printed in your terminal.

✔️ I see PONG

Redis is installed and running. You're ready to move on.

ⓧ I see Could not connect to Redis

Redis installed but the background service didn't start properly.

  • Restart the service by running:
brew services restart redis
  • Wait a few seconds, then try redis-cli ping again.

ⓧ Command not found

The installation did not complete successfully.

  • Make sure Homebrew is up to date by running:
brew update
  • Try installing Redis again by running:
brew install redis
  • After the install finishes, start the service and verify:
brew services start redis
redis-cli ping

You should now see PONG.

Create the project
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir weather-cache-api
cd weather-cache-api
  • Confirm the folder exists by running:
ls ~/Desktop | grep weather-cache-api

You should see weather-cache-api in the output.

  • Open this folder in your code editor by pressing Cmd+Space, typing VS Code (or Cursor), and pressing Enter.
  • In your editor, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to Desktop, select the weather-cache-api folder, and click Open.
  • Create a new file called package.json by clicking File then New File.
  • Paste the following configuration into package.json:
{
  "name": "weather-cache-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node server.js",
    "dev": "node --watch server.js"
  },
  "dependencies": {
    "express": "4.21.2",
    "redis": "5.12.1"
  }
}

What does this configuration do?

Each field has a specific purpose:

  • "type": "module" enables ES module syntax (import/export) and top-level await.
  • The dev script uses Node's built-in --watch flag to auto-restart the server when you save changes.
  • The dependencies section pins Express (4.21.2) for building the API and redis (5.12.1) for connecting to your local Redis server.
  • Save package.json by pressing Cmd+S.
  • Install the dependencies by running this command in your terminal (make sure you're inside the weather-cache-api folder):
npm install

You should see output confirming that 2 packages were added.

Seeing npm errors?

  • Make sure you saved package.json before running npm install.
  • Confirm you're in the weather-cache-api directory by running pwd in your terminal. The path should end with weather-cache-api.
  • Check for typos in your package.json file. JSON requires double quotes and exact comma placement.

Still stuck? Help me fix my npm install error in my weather-cache-api project.

✔️ Awesome, I've got everything!

Great. Double check you've saved package.json and the npm install completed without errors.

ⓧ I'd like to double check the full code

Here's what your package.json should look like:

{
  "name": "weather-cache-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node server.js",
    "dev": "node --watch server.js"
  },
  "dependencies": {
    "express": "4.21.2",
    "redis": "5.12.1"
  }
}
Verify everything works

Before moving on, confirm all three pieces of your setup are healthy.

  • Check your Node.js version by running:
node --version

This project uses ES modules with top-level await and the built-in fetch API, which require Node.js 18 or higher.

✔️ I see version 18 or higher

Your Node.js version is compatible. Move on to the next check.

ⓧ I see an older version

You need Node.js 18+ for this project. Visit the official Node.js downloads page and install the LTS version.

After installing, close and reopen your terminal, then run node --version again to confirm the update.

ⓧ Command not found

Node.js is not installed or not in your PATH.

  • Download and install Node.js from nodejs.org. Choose the LTS version.
  • After installation, close and reopen your terminal.
  • Run node --version again. You should see v18 or higher.
  • Confirm Redis is still running by running:
redis-cli ping

You should see PONG again.

  • Confirm your packages installed correctly by running:
ls node_modules | grep -E 'express|redis'

You should see both express and redis listed in the output.

Missing packages in node_modules?

  • Make sure you're running the command from inside the weather-cache-api folder.
  • If the folder is empty or missing, re-run npm install from inside weather-cache-api.

Need more help? Help me verify my weather-cache-api dependencies are installed correctly.

Your development environment is fully set up. Next, you'll build an Express API that fetches live weather data.

Build the API Without Caching

Your environment is locked and loaded. Redis is running, your dependencies are installed, and your project folder is ready.

Now it's time to build the actual API. In this step, you'll create an Express server that fetches live weather data from the Open-Meteo API. This version has no caching at all. That's intentional. You need a baseline to measure against so the caching improvement in the next step is crystal clear.

In this step, get ready to:
  • Create an Express server with timing middleware.
  • Add a weather endpoint that fetches data from Open-Meteo.
  • Test the endpoint and observe the raw response time.
Create the Express server

Every request to your API takes some amount of time. To measure that, you'll add a timing middleware that stamps the start time onto each incoming request.

  • Create a new file called server.js in your weather-cache-api folder by running:
touch server.js
  • Confirm the file exists by running:
ls

You should see server.js, package.json, and node_modules in the output.

  • Open server.js in your code editor and paste the following code:
import express from 'express';

const app = express();
const PORT = 3000;

// Timing middleware - adds start time to each request
app.use((req, res, next) => {
  req.startTime = Date.now();
  next();
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

What does this code do?

  • import express from 'express' loads the Express framework using ES module syntax (enabled by "type": "module" in your package.json).
  • app.use(...) registers a middleware that runs before every route handler. It records the current timestamp on req.startTime so you can calculate how long the request took later.
  • app.listen(PORT, ...) starts the server on port 3000.
  • Save server.js.
  • Start the dev server by running:
npm run dev

You should see Server running on http://localhost:3000 printed in your terminal.

What is node --watch?

The dev script uses node --watch server.js, which automatically restarts the server whenever you save changes to the file. It's built into Node.js 18+ so you don't need an extra tool like nodemon.

Don't see the server message?

  • Make sure you saved server.js before running the command.
  • Check that your terminal is inside the weather-cache-api folder. Run pwd to confirm.
  • If you see a syntax error, confirm that "type": "module" is in your package.json.

Why isn't my Express server starting?

Add the weather endpoint (no cache)

Open-Meteo is a free weather API that requires no API key. You'll fetch live forecast data from it and return the result directly to the caller. No caching yet.

What is Open-Meteo?

Open-Meteo is a free, open-source weather API. It requires no authentication for non-commercial use, which makes it perfect for learning. Its response times are typically 100-500ms depending on network conditions.

Those response times are slow enough that caching will make a dramatic, measurable difference.

  • In server.js, find this line:
app.listen(PORT, () => {
  • Add the following route handler directly above that line:
// Weather endpoint WITHOUT caching (for comparison)
app.get('/api/weather/no-cache', async (req, res) => {
  const { lat, lon } = req.query;

  if (!lat || !lon) {
    return res.status(400).json({ error: 'lat and lon query parameters are required' });
  }

  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,wind_speed_10m,relative_humidity_2m&timezone=auto`;

  const response = await fetch(url);
  const data = await response.json();

  const duration = Date.now() - req.startTime;
  res.set('X-Cache', 'BYPASS');
  res.set('X-Response-Time', `${duration}ms`);
  return res.json(data);
});

What does this code do?

  • const { lat, lon } = req.query pulls the latitude and longitude from the URL query string (e.g. ?lat=40.71&lon=-74.01).
  • The validation block returns a 400 error if either parameter is missing.
  • fetch(url) calls the Open-Meteo API to get current temperature, wind speed, and humidity.
  • Date.now() - req.startTime calculates total response time using the timestamp from the timing middleware.
  • X-Cache: BYPASS and X-Response-Time headers let you see caching status and timing without digging through the response body.
  • Save server.js. The dev server restarts automatically.

You should see Server running on http://localhost:3000 printed again in your terminal, confirming the restart.

Seeing a syntax error on restart?

  • Make sure the route is placed above app.listen(PORT, ...), not inside it.
  • Check for mismatched backticks in the template literal URL string.
  • Ensure the closing }); of the route handler is present.

Help me fix a syntax error in my Express route handler.

✔️ Awesome, I've got everything!

Great. Double check you've saved server.js and the server restarted without errors.

ⓧ I'd like to double check the full code

import express from 'express';

const app = express();
const PORT = 3000;

// Timing middleware - adds start time to each request
app.use((req, res, next) => {
  req.startTime = Date.now();
  next();
});

// Weather endpoint WITHOUT caching (for comparison)
app.get('/api/weather/no-cache', async (req, res) => {
  const { lat, lon } = req.query;

  if (!lat || !lon) {
    return res.status(400).json({ error: 'lat and lon query parameters are required' });
  }

  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,wind_speed_10m,relative_humidity_2m&timezone=auto`;

  const response = await fetch(url);
  const data = await response.json();

  const duration = Date.now() - req.startTime;
  res.set('X-Cache', 'BYPASS');
  res.set('X-Response-Time', `${duration}ms`);
  return res.json(data);
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});
Test and note the response time

Time to see your API in action. You'll hit the endpoint and observe how long it takes when every request goes directly to the external API with zero caching.

  • Open a new terminal tab or window. Your server is running in the current one, so you need a separate terminal to send requests.
  • Test the weather endpoint by running this command (using New York City coordinates):
curl -i "http://localhost:3000/api/weather/no-cache?lat=40.71&lon=-74.01"

What does the -i flag do?

The -i flag tells curl to display response headers along with the body. This is how you'll see the X-Cache and X-Response-Time headers your middleware sets.

You should see response headers at the top including X-Cache: BYPASS and an X-Response-Time value between 100-500ms, followed by a JSON body with weather data.

  • Run the same command again:
curl -i "http://localhost:3000/api/weather/no-cache?lat=40.71&lon=-74.01"

Notice the response time is roughly the same. Every single request pays the full network cost because there's no caching. That's the problem you'll solve in the next step.

Getting a connection refused error?

  • Make sure your server is still running in the other terminal tab. You should see Server running on http://localhost:3000 there.
  • Check that you're using port 3000 in the URL, not a different port.
  • If the server crashed, switch back to the server terminal and run npm run dev again.

Why is curl giving me connection refused when hitting my Express server?

Your baseline is set. Every request takes 100-500ms because it hits the Open-Meteo API over the network each time. Next up, you'll add Redis caching and watch that number drop to single-digit milliseconds.

Add Redis Caching

Your weather API is working and you can see exactly how long each request takes. The problem is clear: every single request goes out to the Open-Meteo API, even if someone just asked for the same city 10 seconds ago.

In this step, you will connect your server to Redis and implement the cache-aside pattern. This pattern checks the cache first, and only fetches fresh data when the cache is empty. The result is a dramatic speed improvement for repeated requests.

In this step, get ready to:
  • Connect your Express server to the local Redis instance.
  • Implement the cache-aside pattern with a 5-minute TTL.
  • Add cache status headers so you can observe hits and misses.
Connect to Redis

Before your server can cache anything, it needs a connection to your local Redis instance. You will import the redis client library, create a connection, and define how long cached data should live.

  • In server.js, find this line at the top of the file:
import express from 'express';
  • Add this import directly below it:
import { createClient } from 'redis';
  • Next, find this line:
const PORT = 3000;
  • Add the following code below it to set up the Redis connection:
const CACHE_TTL = 300; // 5 minutes in seconds

// Create and connect Redis client
const redisClient = createClient();
redisClient.on('error', (err) => console.log('Redis Client Error', err));
await redisClient.connect();
console.log('Connected to Redis');

What does this code do?

CACHE_TTL sets how long cached data stays alive (300 seconds = 5 minutes). After this time, Redis automatically deletes the entry so the next request fetches fresh data.

createClient() connects to Redis on localhost:6379 by default. The error listener logs any connection issues, and await redisClient.connect() establishes the connection before the server starts handling requests.

What is TTL?

TTL stands for Time-To-Live. It tells Redis to automatically delete a cached entry after a set number of seconds. This prevents your cache from serving stale data forever. For weather data that updates roughly every 15 minutes, a 5-minute TTL strikes a good balance between freshness and performance.

  • Save server.js.
  • Switch back to the terminal where your server is running. If you started it with npm run dev, it restarts automatically. You should see Connected to Redis printed in the terminal output.

Seeing a Redis Client Error?

Make sure your Redis server is still running. Open a new terminal tab and run redis-cli ping. If you see PONG, Redis is running. If not, start it again with brew services start redis.

help me fix a Redis connection error in my Node.js Express app

Implement the cache-aside pattern

The cache-aside pattern follows a simple flow: check if the data is already in the cache, return it if found (a cache hit), and fetch from the external API only if the cache is empty (a cache miss). After fetching, store the result in the cache for next time.

  • In server.js, find the timing middleware block:
// Timing middleware - adds start time to each request
app.use((req, res, next) => {
  req.startTime = Date.now();
  next();
});
  • Add the following cached weather endpoint between the timing middleware and the existing /api/weather/no-cache route:
// Weather endpoint WITH caching (cache-aside pattern)
app.get('/api/weather', async (req, res) => {
  const { lat, lon } = req.query;

  if (!lat || !lon) {
    return res.status(400).json({ error: 'lat and lon query parameters are required' });
  }

  const cacheKey = `weather:${lat}:${lon}`;

  // Step 1: Check the cache
  const cachedData = await redisClient.get(cacheKey);

  if (cachedData) {
    // Cache HIT - return cached data
    const duration = Date.now() - req.startTime;
    res.set('X-Cache', 'HIT');
    res.set('X-Response-Time', `${duration}ms`);
    return res.json(JSON.parse(cachedData));
  }

  // Step 2: Cache MISS - fetch from Open-Meteo API
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,wind_speed_10m,relative_humidity_2m&timezone=auto`;

  const response = await fetch(url);
  const data = await response.json();

  // Step 3: Store in Redis with TTL
  await redisClient.set(cacheKey, JSON.stringify(data), { EX: CACHE_TTL });

  const duration = Date.now() - req.startTime;
  res.set('X-Cache', 'MISS');
  res.set('X-Response-Time', `${duration}ms`);
  return res.json(data);
});

What does this code do?

The endpoint builds a cache key like weather:40.71:-74.01 from the latitude and longitude. This means each unique location gets its own cache entry.

redisClient.get(cacheKey) checks if data already exists for that key. If it does, the endpoint returns the cached JSON immediately without ever calling Open-Meteo.

On a cache miss, redisClient.set(cacheKey, JSON.stringify(data), { EX: CACHE_TTL }) stores the response in Redis. The EX option sets the TTL in seconds, so the entry automatically expires after 5 minutes.

Why this key design?

Redis keys use colon-separated namespaces by convention. The pattern weather:${lat}:${lon} makes keys readable and easy to scan later. If you ever need to find all weather cache entries, you can search for keys matching weather:*.

  • Save server.js.
  • Your server should automatically restart. Check the terminal for Connected to Redis (no errors).

Server crashing on restart?

Check that the new /api/weather route is placed AFTER the timing middleware and BEFORE the existing /api/weather/no-cache route. Also confirm there are no missing closing braces or parentheses in the route handler.

help me debug why my Express server crashes after adding the cached weather endpoint

Add cache status headers

Your cached endpoint already sets two custom response headers. These give you instant visibility into whether caching is working without reading the response body.

  • Test the cached endpoint by running this command in a new terminal tab:
curl -i "http://localhost:3000/api/weather?lat=40.71&lon=-74.01"

What do these headers tell you?

X-Cache tells you whether the response came from the cache (HIT) or from a fresh API call (MISS). The no-cache endpoint sets this to BYPASS so you always know which path was used.

X-Response-Time shows exactly how many milliseconds the request took. This is how you will prove the performance difference in the next step.

  • Look at the response headers in your terminal output. You should see X-Cache: MISS and an X-Response-Time value (likely 200-500ms). This first request fetched from Open-Meteo and stored the result in Redis.
  • Run the same command again:
curl -i "http://localhost:3000/api/weather?lat=40.71&lon=-74.01"
  • This time you should see X-Cache: HIT and a much lower X-Response-Time (typically 1-5ms). The data came straight from Redis without touching the external API.

Still seeing X-Cache: MISS on the second request?

Make sure you are using the exact same lat and lon values in both requests. The cache key is built from these coordinates, so even a small difference creates a separate entry.

Also verify your Redis connection is active by checking for Connected to Redis in your server terminal. If the connection dropped, the redisClient.set() call would have failed silently.

help me figure out why my Redis cache is not returning hits on repeated requests

✔️ Awesome, I've got everything!

Great. Double check you have saved server.js and your server is running without errors.

ⓧ I'd like to double check the full code

Here is the complete server.js at this point:

import express from 'express';
import { createClient } from 'redis';

const app = express();
const PORT = 3000;
const CACHE_TTL = 300; // 5 minutes in seconds

// Create and connect Redis client
const redisClient = createClient();
redisClient.on('error', (err) => console.log('Redis Client Error', err));
await redisClient.connect();
console.log('Connected to Redis');

// Timing middleware - adds start time to each request
app.use((req, res, next) => {
  req.startTime = Date.now();
  next();
});

// Weather endpoint WITH caching (cache-aside pattern)
app.get('/api/weather', async (req, res) => {
  const { lat, lon } = req.query;

  if (!lat || !lon) {
    return res.status(400).json({ error: 'lat and lon query parameters are required' });
  }

  const cacheKey = `weather:${lat}:${lon}`;

  // Step 1: Check the cache
  const cachedData = await redisClient.get(cacheKey);

  if (cachedData) {
    // Cache HIT - return cached data
    const duration = Date.now() - req.startTime;
    res.set('X-Cache', 'HIT');
    res.set('X-Response-Time', `${duration}ms`);
    return res.json(JSON.parse(cachedData));
  }

  // Step 2: Cache MISS - fetch from Open-Meteo API
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,wind_speed_10m,relative_humidity_2m&timezone=auto`;

  const response = await fetch(url);
  const data = await response.json();

  // Step 3: Store in Redis with TTL
  await redisClient.set(cacheKey, JSON.stringify(data), { EX: CACHE_TTL });

  const duration = Date.now() - req.startTime;
  res.set('X-Cache', 'MISS');
  res.set('X-Response-Time', `${duration}ms`);
  return res.json(data);
});

// Weather endpoint WITHOUT caching (for comparison)
app.get('/api/weather/no-cache', async (req, res) => {
  const { lat, lon } = req.query;

  if (!lat || !lon) {
    return res.status(400).json({ error: 'lat and lon query parameters are required' });
  }

  const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,wind_speed_10m,relative_humidity_2m&timezone=auto`;

  const response = await fetch(url);
  const data = await response.json();

  const duration = Date.now() - req.startTime;
  res.set('X-Cache', 'BYPASS');
  res.set('X-Response-Time', `${duration}ms`);
  return res.json(data);
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Your caching layer is live. Next up, you will measure exactly how much faster cached requests are compared to uncached ones.

Measure the Performance Difference

Your caching layer is live. The /api/weather endpoint now checks Redis before reaching out to the external API.

But how much faster is it really? In this step, you'll run the same request twice and see the numbers for yourself. Then you'll peek inside Redis to understand exactly what got stored and watch what happens when the TTL expires.

In this step, get ready to:
  • Compare response times between cached and uncached requests.
  • Inspect the stored cache data using redis-cli.
  • Observe TTL expiration and automatic cache refresh.
Compare cached vs uncached response times

The real proof of caching is in the numbers. You'll hit the cached endpoint twice. The first request will be a cache MISS (Redis is empty, so it fetches from Open-Meteo). The second request will be a cache HIT (data already in Redis).

  • Open a new terminal tab or window (your server is still running in the first one).
  • Hit the cached weather endpoint by running this command:
curl -i "http://localhost:3000/api/weather?lat=40.71&lon=-74.01"

Look at the response headers near the top of the output. You should see X-Cache: MISS and an X-Response-Time value around 200-500ms.

What do these headers mean?

Your timing middleware records how long the full request took. X-Cache: MISS means Redis had no data for this key, so the server fetched from Open-Meteo. That network round-trip is what makes it slow.

  • Hit the exact same endpoint again by running the same command:
curl -i "http://localhost:3000/api/weather?lat=40.71&lon=-74.01"

This time you should see X-Cache: HIT and an X-Response-Time of about 1-5ms. That is a 50-100x speedup from a single line of cache logic.

  • Now compare against the no-cache endpoint by running:
curl -i "http://localhost:3000/api/weather/no-cache?lat=40.71&lon=-74.01"

You should see X-Cache: BYPASS and a response time back in the 200-500ms range. Every single request to this endpoint goes to Open-Meteo, no matter how many times you call it.

Not seeing the headers?

Make sure you include the -i flag in your curl command. Without it, curl only shows the response body and hides the headers.

If you see a connection error, confirm your server is still running in the other terminal tab.

Why aren't my caching headers showing up?

Inspect the cache with redis-cli

The response headers prove caching works, but you can also look directly at what Redis stored. The redis-cli tool lets you query the Redis server interactively.

  • Open the Redis command-line interface by running:
redis-cli
  • List all keys stored in Redis by typing:
keys *

You should see at least one key named weather:40.71:-74.01 (matching the coordinates you used in your curl request).

  • View the cached data for that key by typing:
get weather:40.71:-74.01

You should see the full JSON weather response stored as a string. This is exactly what your server returns on a cache HIT without calling Open-Meteo.

  • Check how many seconds remain before this key expires by typing:
ttl weather:40.71:-74.01

You should see a number less than 300 (since some time has passed since the key was stored). This is the remaining TTL in seconds.

What is TTL telling you?

TTL stands for Time-To-Live. When your server stored this key, it set a 300-second (5-minute) expiration. Redis counts down from 300 and automatically deletes the key when it reaches zero.

This ensures your weather data stays fresh. After 5 minutes, the next request triggers a new fetch from Open-Meteo, keeping results reasonably current.

Seeing (nil) for the key?

If get weather:40.71:-74.01 returns (nil), the TTL may have already expired. Run your curl command again to re-populate the cache, then check redis-cli immediately.

Help me understand why my Redis key is missing.

Watch TTL expiration in action

Seeing the TTL count down is one thing. Watching it actually expire and trigger a fresh fetch proves the full lifecycle of your cache-aside pattern.

  • Switch back to redis-cli and force the key to expire in 5 seconds by typing:
EXPIRE weather:40.71:-74.01 5

You should see (integer) 1 confirming the new TTL was set.

What does EXPIRE do?

The EXPIRE command overrides the remaining TTL on an existing key. You're telling Redis to delete this key after 5 seconds instead of waiting for the original countdown to finish. This lets you test expiration without waiting 5 full minutes.

  • Wait about 6 seconds, then confirm the key is gone by typing:
get weather:40.71:-74.01

You should see (nil) confirming Redis automatically deleted the key.

  • Exit redis-cli by typing:
exit
  • Now hit the cached endpoint one more time:
curl -i "http://localhost:3000/api/weather?lat=40.71&lon=-74.01"

You should see X-Cache: MISS again with a slower response time. The cache was empty, so your server fetched fresh data from Open-Meteo and stored it in Redis with a new 5-minute TTL.

The full cache lifecycle

You just witnessed the complete cache-aside loop: data is fetched and cached on the first request, served instantly from cache on subsequent requests, automatically deleted when the TTL expires, then re-fetched and re-cached on the next request after expiration.

This keeps data fresh without any manual intervention. In production, you would tune the TTL based on how frequently the source data changes.

Still seeing a cache HIT after expiration?

Make sure you waited long enough after running EXPIRE. If you re-ran the curl command before the 5 seconds elapsed, the key was still alive and you got a HIT. Try the sequence again with a longer wait.

Why is my key not expiring as expected?

Secret mission

Add Cache Management Endpoints

Your cache works, but you have no way to manage it. What if weather data changes unexpectedly? What if you want to see what's cached? Add invalidation and statistics endpoints to control your cache like a production system.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Redis server (running as a background service via Homebrew).
  • The weather-cache-api project folder on your Desktop.

Keep everything running

No action needed. Choose this if you're still actively building or want to experiment further with caching strategies.

  • Your Express server continues running in the terminal where you started it.
  • Redis continues running in the background and will restart automatically at login.
  • Your project folder and all code remain on your Desktop.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your project files stay intact so you can pick up where you left off.

  • Stop the Express server by pressing Ctrl+C in the terminal where it's running.
  • Stop the Redis background service by running:
brew services stop redis
  • Your project folder and code remain on your Desktop. When you're ready to resume, start Redis again with brew services start redis and run npm run dev from your project folder.

Delete - I don't want to use this again

Remove all project resources and start fresh. This uninstalls Redis and deletes your project files.

  • Stop the Express server by pressing Ctrl+C in the terminal where it's running.
  • Stop the Redis background service by running:
brew services stop redis
  • Uninstall Redis by running:
brew uninstall redis
  • Delete your project folder by running:
cd ~/Desktop
rm -rf weather-cache-api
  • Confirm the folder is gone by running:
ls ~/Desktop | grep weather

You should see no output, confirming the folder has been removed.

That's a Wrap!

That's a Wrap!

Nice work! You just built a production-grade caching layer from scratch using Redis and Express.

You've learned how to:

  • Built an Express API that fetches real weather data from Open-Meteo and implemented the cache-aside pattern with Redis.
  • Added TTL-based expiration so stale data automatically refreshes, and cache status headers (X-Cache, X-Response-Time) for observability.
  • Measured the performance impact with a clear before-and-after comparison: ~200-500ms down to ~1-5ms.
  • Secret Mission: Built cache invalidation and statistics endpoints for full cache management.

Ready to quiz yourself?