Caching in JavaScript: Redis, Memcached, and Built-in Solutions (Advanced Level)
In modern web development, application performance often hinges on data access speed. Every extra query to a database or external API increases response time and server load. Caching is not just about "saving a result" — it's a whole science involving choosing the right strategy, tool, and invalidation mechanism. In this article, we will explore advanced caching techniques in Node.js: from built-in solutions to industrial-grade systems like Redis and Memcached.
1. Built-in Caching in Node.js: Simplicity and Limitations
Before integrating external services, it's worth evaluating the capabilities of Node.js itself. Built-in caching is implemented using Map or WeakMap objects, as well as simple global variables. This is ideal for data that rarely changes and lives within a single process.
// Simple in-memory cache with TTL (Time To Live)class MemoryCache { constructor(ttl = 60000) { this.cache = new Map(); this.ttl = ttl; }
set(key, value) { this.cache.set(key, { value, expiry: Date.now() + this.ttl }); }
get(key) { const item = this.cache.get(key); if (!item) return null; if (Date.now() > item.expiry) { this.cache.delete(key); return null; } return item.value; }
delete(key) { this.cache.delete(key); }
clear() { this.cache.clear(); }}
// Usageconst cache = new MemoryCache(30000); // 30 seconds TTLcache.set('user:42', { name: 'Alice', role: 'admin' });console.log(cache.get('user:42')); // { name: 'Alice', role: 'admin' }Pros: zero network latency, simple implementation, no external dependencies required.
Cons: data is lost on process restart, does not scale across multiple servers, limited by the RAM of a single node.
For microservices running in a single process, or for caching the results of heavy computations (e.g., template rendering), a built-in solution is an excellent choice. However, it is insufficient for distributed systems.
2. Redis: The King of Caching in Node.js
Redis is an in-memory data store often used as a high-performance cache. Its main advantages include support for complex data structures (strings, lists, sets, hashes), built-in TTL, pub/sub, and disk persistence.
Connecting Redis in Node.js
To work with Redis in Node.js, the ioredis package (recommended) or redis package is used. Let's look at an example with ioredis:
const Redis = require('ioredis');
const redis = new Redis({ host: 'localhost', port: 6379, retryStrategy: (times) => Math.min(times * 50, 2000) // reconnection strategy});
// Basic operationsawait redis.set('mykey', 'Hello, Redis!', 'EX', 60); // TTL 60 secondsconst value = await redis.get('mykey');console.log(value); // "Hello, Redis!"
// Using a hash to store complex objectsawait redis.hmset('user:100', { name: 'Bob', email: 'bob@example.com', age: 30});const user = await redis.hgetall('user:100');console.log(user); // { name: 'Bob', email: 'bob@example.com', age: '30' }Advanced Strategies with Redis
Cache-Aside (Lazy Loading): The application first checks the cache, and only on a miss does it query the database and populate the cache.
async function getUser(userId) { const cacheKey = `user:${userId}`; let user = await redis.get(cacheKey); if (user) { return JSON.parse(user); } // Cache miss — load from DB user = await db.findUserById(userId); if (user) { await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // cache for 1 hour } ret