StructuralTypeScriptverifiedVerified
Proxy Pattern in TypeScript
Provides a surrogate or placeholder for another object to control access, add lazy initialization, caching, logging, or access control.
How to Implement the Proxy Pattern in TypeScript
1Step 1: Define the subject interface and real implementation
interface Subject {
request(): void;
}
class RealSubject implements Subject {
request(): void {
console.log("RealSubject: handling request.");
}
}2Step 2: Create the proxy with access control and logging
class ProxySubject implements Subject {
private realSubject: RealSubject;
constructor(realSubject: RealSubject) {
this.realSubject = realSubject;
}
private checkAccess(): boolean {
console.log("Proxy: checking access before forwarding request.");
return true; // simplified – could consult auth service
}
private logAccess(): void {
console.log("Proxy: logging time of request.");
}
request(): void {
if (this.checkAccess()) {
this.realSubject.request();
this.logAccess();
}
}
}3Step 3: Access the real subject through the proxy
function clientCode(subject: Subject): void {
subject.request();
}
const real = new RealSubject();
clientCode(real);
console.log("---");
const proxy = new ProxySubject(real);
clientCode(proxy);// Proxy Pattern – Production
// Caching proxy with per-key TTL sitting in front of any async data fetcher.
export interface DataSource<T> {
fetch(key: string): Promise<T>;
}
interface CacheEntry<T> {
value: T;
expiresAt: number;
}
export class CachingProxy<T> implements DataSource<T> {
private cache = new Map<string, CacheEntry<T>>();
constructor(
private readonly origin: DataSource<T>,
private readonly ttlMs: number = 60_000,
) {}
async fetch(key: string): Promise<T> {
const entry = this.cache.get(key);
if (entry && Date.now() < entry.expiresAt) {
console.log(`[Cache HIT] key="${key}"`);
return entry.value;
}
console.log(`[Cache MISS] key="${key}" – fetching from origin`);
const value = await this.origin.fetch(key);
this.cache.set(key, { value, expiresAt: Date.now() + this.ttlMs });
return value;
}
invalidate(key: string): void {
this.cache.delete(key);
console.log(`[Cache] invalidated key="${key}"`);
}
invalidateAll(): void {
this.cache.clear();
console.log("[Cache] cleared all entries");
}
}
// ── Example origin ────────────────────────────────────────────────────────────
interface WeatherData { temp: number; condition: string }
class WeatherApiClient implements DataSource<WeatherData> {
async fetch(city: string): Promise<WeatherData> {
console.log(`[API] GET /weather?city=${city}`);
// Simulate network delay + response
await new Promise((r) => setTimeout(r, 50));
return { temp: 22, condition: "sunny" };
}
}
// ── Usage ─────────────────────────────────────────────────────────────────────
const proxy = new CachingProxy(new WeatherApiClient(), 5_000);
(async () => {
await proxy.fetch("London"); // MISS – calls API
await proxy.fetch("London"); // HIT – served from cache
proxy.invalidate("London");
await proxy.fetch("London"); // MISS – cache was cleared
})();Proxy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Proxy Pattern in the Real World
“A corporate receptionist acts as a proxy for the CEO. When someone wants to meet the CEO, the receptionist checks credentials, schedules the meeting, and logs the visit before granting access. The visitor interacts with the receptionist using the same protocol they would use with the CEO—the receptionist simply adds control and record-keeping around that access.”