Implementing Efficient Caching in Node.js with node-cache

November 2, 2024 (2w ago)

Implementing Efficient Caching in Node.js with node-cache

Caching is a powerful way to improve the performance and responsiveness of applications by storing frequently accessed data in memory. For Node.js applications, node-cache provides an efficient in-memory caching solution that is simple to set up and ideal for caching data with a limited lifespan. With node-cache, you can store, retrieve, and manage cache entries directly within your Node.js process, making it perfect for small- to medium-sized applications that don’t require distributed caching.

In this guide, we’ll explore setting up node-cache, managing cache entries, configuring expiration policies, and best practices to ensure optimal cache performance in Node.js.


Why Use node-cache for Caching?

node-cache is a lightweight, in-memory caching solution designed for Node.js. Key benefits include:

  1. Simplicity: node-cache is easy to set up and use with minimal configuration.
  2. Performance: As an in-memory cache, it provides very low-latency access to cached data.
  3. Data Expiration: Built-in expiration policies ensure data is removed from cache when it’s no longer needed.
  4. Event-Based: Supports events like cache hits, misses, and expired keys for improved cache management.

Note: node-cache is not a distributed cache and should only be used when data does not need to be shared across multiple servers. For distributed caching, consider Redis or Memcached.


Setting Up node-cache in Node.js

Step 1: Install node-cache

To get started, install node-cache in your Node.js project.

npm install node-cache

Step 2: Configure and Initialize node-cache

Create a cache.js file to configure and export an instance of node-cache.

cache.js

const NodeCache = require("node-cache");
const cache = new NodeCache({ stdTTL: 3600, checkperiod: 120 });
 
module.exports = cache;

Configuration Options:

This configuration sets a default expiration time of 1 hour for cache entries and automatically checks for expired entries every 2 minutes.


Using node-cache to Store and Retrieve Data

With the cache instance configured, you can now add, retrieve, and manage cache entries in your application.

1. Caching Data with set

Use cache.set to add data to the cache with an optional custom TTL.

const cache = require("./cache");
 
// Cache a user profile with a 1-hour TTL
cache.set("user:123", { name: "Alice", age: 30 });
 
// Cache a temporary value with a 10-minute TTL
cache.set("temp:123", "temporary_value", 600); // 600 seconds or 10 minutes

2. Retrieving Data with get

Use cache.get to retrieve cached data. If the data doesn’t exist or has expired, it returns undefined.

const user = cache.get("user:123");
if (user) {
  console.log("User retrieved from cache:", user);
} else {
  console.log("User not found in cache or has expired.");
}

3. Checking Cache Existence with has

The has method checks if a key exists in the cache, useful when you only need to know if the data is cached.

if (cache.has("user:123")) {
  console.log("User is cached.");
} else {
  console.log("User is not cached.");
}

Using node-cache for Application-Specific Use Cases

Example 1: Caching Database Queries

Database queries can be resource-intensive, especially for frequently requested data. Use node-cache to cache query results and avoid repeated database calls.

database.js

const cache = require("./cache");
const db = require("./db"); // Mock database module
 
const getUserById = async (userId) => {
  // Check if user data is cached
  const cachedUser = cache.get(`user:${userId}`);
  if (cachedUser) {
    return cachedUser;
  }
 
  // Fetch user from database if not cached
  const user = await db.getUserById(userId);
  cache.set(`user:${userId}`, user, 3600); // Cache for 1 hour
  return user;
};

With this setup:

Example 2: Caching API Responses

For applications that make external API calls, cache responses to reduce redundant requests.

const cache = require("./cache");
const axios = require("axios");
 
const fetchWeatherData = async (city) => {
  const cachedWeather = cache.get(`weather:${city}`);
  if (cachedWeather) {
    return cachedWeather;
  }
 
  const response = await axios.get(`https://api.weather.com/${city}`);
  const weatherData = response.data;
  cache.set(`weather:${city}`, weatherData, 600); // Cache for 10 minutes
  return weatherData;
};

By caching API responses, you minimize API usage, reduce latency, and improve application performance.


Managing Cache Entries with node-cache

node-cache provides several methods to manage cache entries effectively.

1. Deleting Cache Entries with del

Use cache.del to remove specific entries from the cache.

cache.del("user:123");

2. Flushing All Entries with flushAll

Clear the entire cache if you need to reset or invalidate all entries.

cache.flushAll();

3. Caching Multiple Entries with mset and Retrieving with mget

Use mset to add multiple entries at once and mget to retrieve multiple entries.

// Set multiple cache entries
cache.mset([
  { key: "user:123", val: { name: "Alice", age: 30 }, ttl: 3600 },
  { key: "user:456", val: { name: "Bob", age: 25 }, ttl: 3600 },
]);
 
// Retrieve multiple cache entries
const users = cache.mget(["user:123", "user:456"]);
console.log(users);

Handling Cache Events for Monitoring and Logging

node-cache provides events like expired, set, and del to monitor cache activity and track cache hits or misses.

Monitoring Expired Entries

Listen to the expired event to log or take action when cache entries expire.

cache.on("expired", (key, value) => {
  console.log(`Cache expired for key: ${key}`);
});

Logging Cache Hits and Misses

Track cache hits and misses by checking if get returns a value. This can be useful for optimizing frequently accessed data.

const getCachedData = (key) => {
  const data = cache.get(key);
  if (data) {
    console.log(`Cache hit for key: ${key}`);
    return data;
  } else {
    console.log(`Cache miss for key: ${key}`);
    return null;
  }
};

Best Practices for Using node-cache in Node.js

  1. Use Unique Keys: Use consistent and unique keys for caching (e.g., prefix keys with identifiers like user: or product:) to avoid key conflicts.
  2. Set Appropriate TTL: Choose TTL based on data volatility. For frequently updated data, use shorter TTLs, and for rarely changed data, use longer TTLs.
  3. Monitor Cache Usage: Track cache hits and misses to identify frequently accessed data and optimize cache TTL settings.
  4. Handle Cache Expiration Gracefully: Be prepared for cache misses by having fallback data sources (e.g., database or API) to fetch data when cache entries expire.
  5. Avoid Over-Caching: Cache only frequently accessed or expensive-to-compute data. Over-caching can consume memory unnecessarily and lead to stale data.
  6. Periodic Cache Cleaning: Use the checkperiod configuration to remove expired entries periodically and free up memory.

Advanced Use Cases for node-cache

1. Implementing Request Throttling

Use node-cache to implement basic request throttling for API endpoints, tracking the number of requests per user.

const throttleRequest = (userId) => {
  const requests = cache.get(`throttle:${userId}`) || 0;
 
  if (requests >= 10) {
    return false; // Throttle user if limit reached
  }
 
  cache.set(`throttle:${userId}`, requests + 1, 60); // Set TTL to 1 minute
  return true;
};

2. Session Storage for Lightweight Applications

node-cache can handle lightweight session storage by caching session data with a reasonable TTL.

const storeSession = (sessionId, data) => {
  cache.set(`session:${sessionId}`, data, 1800); // 30
 
-minute session
};
 
const getSession = (sessionId) => {
  return cache.get(`session:${sessionId}`);
};

Conclusion

Using node-cache in Node.js provides a fast, efficient, and easy-to-implement solution for caching data within an application process. By setting up node-cache with thoughtful TTLs, monitoring cache performance, and following best practices, you can significantly improve the speed and scalability of your application.

Integrate these caching techniques in your Node.js applications to reduce latency, lower database load, and enhance user experience, all while keeping your caching solution lightweight and manageable.