Caching in TypeScript: Redis, Memcached, and Built-in Solutions (Advanced Level)

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Advanced Caching is a Necessity for TypeScript Applications



In modern high-load systems, caching is no longer an option — it is a mandatory architectural element. For TypeScript developers working with Node.js, choosing the right caching strategy can reduce API response time from 200 ms to 2 ms and decrease database load by 80-90%. In this article, we will examine three advanced caching approaches: Redis, Memcached, and built-in TypeScript solutions.



1. Built-in Caching in TypeScript: From Simple Map to LRU Cache



Built-in solutions are ideal for monolithic applications or microservices where a distributed cache is not required. The most primitive option is using Map, but it is not suitable for production due to the lack of TTL and memory control.



Implementing an LRU Cache with TTL



Let's look at an advanced implementation of an LRU (Least Recently Used) cache with automatic eviction:



interface CacheEntry<T> {  value: T;  expiresAt: number;}

class LRUCache<T> { private cache: Map<string, CacheEntry<T>>; private readonly maxSize: number; private readonly ttlMs: number;

constructor(maxSize: number = 100, ttlMs: number = 60000) { this.cache = new Map(); this.maxSize = maxSize; this.ttlMs = ttlMs; }

get(key: string): T | undefined { const entry = this.cache.get(key); if (!entry) return undefined;

if (Date.now() > entry.expiresAt) { this.cache.delete(key); return undefined; }

// Move the key to the end (most recent) this.cache.delete(key); this.cache.set(key, entry); return entry.value; }

set(key: string, value: T): void { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.maxSize) { // Remove the oldest (first) element const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); }

this.cache.set(key, { value, expiresAt: Date.now() + this.ttlMs, }); }

clear(): void { this.cache.clear(); }}

// Usageconst userCache = new LRUCache<User>(500, 300000); // 500 items, 5 minutes TTL


Advanced Pattern: Caching Decorator



For cleaner code, use TypeScript decorators:



function Cached(ttlMs: number = 60000) {  const cache = new LRUCache<any>(100, ttlMs);

return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value;

descriptor.value = async function (...args: any[]) { const key = `${propertyKey}_${JSON.stringify(args)}`; const cached = cache.get(key); if (cached !== undefined) return cached;

const result = await originalMethod.apply(this, args); cache.set(key, result); return result; };

return descriptor; };}

class UserService { @Cached(120000) // 2 minutes async getUser(id: string): Promise<User> { // Expensive DB query return db.findUser(id); }}


2. Redis: Distributed Caching with Advanced Data Structures



Redis is not just a key-value store. Its support for complex data structures (Hash, List, Set, Sorted Set) opens up possibilities for implementing advanced caching strategies.



Tag-Based Cache Invalidation



Problem: when updating one entity, multiple caches need to be invalidated. Solution — tags:



import Redis from 'ioredis';

class TaggedCache { private redis: Redis; private readonly keyPrefix: string;

constructor(redisUrl: string, prefix: string = 'cache') { this.redis = new Redis(redisUrl); this.keyPrefix = prefix; }

async get<T>(key: string,

Blogs

Book Recommendations