Proxy Pattern in TypeScript — ✨ Structural Design Pattern #6

365kim
3 min readJul 1, 2023

--

Photo by Hermes Rivera on Unsplash

🙋🏻‍♀️ What is Proxy Pattern?

The Proxy Pattern is a structural design pattern that provides a placeholder for another object. It allows you to control access to the original object so that it can enhance the object’s functionality, provide lazy initialization, implement access control, or perform caching.

By definition, a proxy means a middleman or a substitute to represent someone else. The proxy acts as an intermediary between the client and the real object, intercepting requests and managing their execution.

✨ Simple TypeScript Example

Let’s make a HttpServiceProxy that improves performance and reduces network traffic.

First, define Subject interface, which represents the common interface for both RealHttpService and HttpServiceProxy.

type MethodType = Request['method']
type UrlType = Request['url']
type BodyType = Request['body']

interface Subject {
request(method: MethodType, url: UrlType, body?: BodyType): Promise<any>
}

Then, RealHttpService represents the real implementation of the HTTP service. The request method sends the HTTP request using the Fetch API, receives the response, and converts it to JSON before returning the data.

class RealHttpService implements Subject {
async request(method: MethodType, url: UrlType, body?: BodyType) {
return fetch(url, {
method,
body,
}).then((response) => response.json())
}
}

Now, HttpServiceProxy, which implements the Subject interface as well, acts as a proxy for the RealHttpService adding caching functionality.

type CacheKey = `${MethodType}-${UrlType}`

class HttpServiceProxy implements Subject {
private readonly realHttpService: RealHttpService
private readonly cache: Map<CacheKey, any> = new Map()

constructor() {
this.realHttpService = new RealHttpService()
}

async request(method: MethodType, url: UrlType, body?: BodyType) {
const cacheKey: CacheKey = `${method}-${url}`

if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)
}

const response = await this.realHttpService.request(method, url, body)

this.cache.set(cacheKey, response) // for future use
return response
}
}

If the client call asyncGetCake the first time with await asyncGetCake(), it makes a request to the server through the httpService proxy, which in turn makes the request using the realHttpService.

// Client Code
const httpService = new HttpServiceProxy()

async function asyncGetCake() {
await httpService.request('GET', 'https://exmaple.com/cake').then((response) => {
console.log(response)
})
}

await asyncGetCake() // This will make a request to the server
await asyncGetCake() // This will use the cached response

When the client call asyncGetCake the second time, it uses the cached response stored in the cache of the HttpServiceProxy. As a result, it doesn't make a request to the server again.

🧑🏻‍💻 Use it or Avoid it

When to use it

Common use cases are:

  1. Remote Proxy: to provide a local representation of a remote object
  2. Virtual Proxy: to create expensive objects on demand(lazy initialization), optimizing resource usage and initialization time.
  3. Protection Proxy: to control access to sensitive objects or operations by implementing access control mechanisms.
  4. Caching Proxy: to cache the results of expensive operations.
  5. Logging Proxy: to add logging or auditing functionality to the operations of an object.

When to avoid it

  • If the object being proxied is not complex nor have to add additional functionality or control over its operations.
  • If frequent object creation or method calls are involved.

--

--

365kim

Web Front-End Developer who believes Every Day Counts!