Control, validate, or extend access to objects in powerful ways without touching their original code.

Introduction: Why Proxy?
Have you ever wanted to:
- Log every time a property is read?
- Validate object fields before setting them?
- Lazy-load or cache data from an API?
- Add access control without rewriting classes?
That’s exactly what the Proxy Pattern is for.
In classic design patterns, a Proxy acts as a stand-in (or “middleman”) between the client and the real object. In JavaScript, ES6 made this trivial with the built-in Proxy
object.
👉 Think of it as a gatekeeper: all reads, writes, or method calls go through your proxy first, and you decide what to do.
Let’s dive into real-world examples of the Proxy Pattern in JavaScript.
1. Property Logging (Monitor Usage)
Want to know how often a property gets accessed?
const user = { name: "Ali", age: 25 };
const userProxy = new Proxy(user, {
get(target, prop) {
console.log(`Property "${prop}" was accessed`);
return target[prop];
}
});
console.log(userProxy.name); // logs + returns "Ali"
console.log(userProxy.age); // logs + returns 25
✅ Use case: Debugging, analytics, monitoring suspicious data reads.
2. Validation Before Setting Properties
Ensure only valid data enters your object.
const user = { name: "Sara", age: 20 };
const validator = {
set(target, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new TypeError("Age must be a number");
}
target[prop] = value;
return true;
}
};
const userProxy = new Proxy(user, validator);
userProxy.age = 30; // ✅ Works
userProxy.age = "thirty"; // ❌ Throws error
✅ Use case: Form handling, model validation, API response sanitization.
3. Default Values with Proxies
Prevent undefined
surprises by providing fallbacks.
const defaults = {
name: "Anonymous",
role: "guest"
};
const safeUser = new Proxy({}, {
get: (target, prop) => prop in target ? target[prop] : defaults[prop]
});
console.log(safeUser.name); // "Anonymous"
safeUser.name = "Omar";
console.log(safeUser.name); // "Omar"
✅ Use case: Safe config objects, missing API response fields.
4. Access Control (Security Check)
You can enforce permissions transparently.
function createSecureObject(obj, allowedFields) {
return new Proxy(obj, {
get(target, prop) {
if (!allowedFields.includes(prop)) {
throw new Error(`Access to "${prop}" is denied`);
}
return target[prop];
}
});
}
const employee = { id: 1, name: "Fatima", salary: 5000 };
const secureEmployee = createSecureObject(employee, ["id", "name"]);
console.log(secureEmployee.name); // ✅ "Fatima"
console.log(secureEmployee.salary); // ❌ Error
✅ Use case: Hide sensitive fields like passwords, tokens, or salaries.
5. Virtual Proxy (Lazy Loading)
Delay expensive object creation until it’s actually needed.
function heavyResource() {
console.log("Heavy resource created!");
return { data: "Expensive data" };
}
let resourceProxy = new Proxy({}, {
get: (target, prop) => {
if (!target.instance) {
target.instance = heavyResource();
}
return target.instance[prop];
}
});
console.log(resourceProxy.data); // creates + logs
console.log(resourceProxy.data); // reuses
✅ Use case: Large data objects, files, or API responses that should only load on demand.
6. Caching Proxy
Speed up repeated expensive operations.
function slowSquare(n) {
console.log(`Calculating square for ${n}...`);
return n * n;
}
const cache = {};
const squareProxy = new Proxy(slowSquare, {
apply(target, thisArg, args) {
const n = args[0];
if (n in cache) return cache[n];
cache[n] = target(n);
return cache[n];
}
});
console.log(squareProxy(5)); // calculates
console.log(squareProxy(5)); // cached
✅ Use case: Memoization for API calls, DB queries, expensive computations.
7. API Request Wrapper
Automatically add headers or handle errors in a fetch API.
function createApi(baseURL) {
return new Proxy(fetch, {
apply: async (target, thisArg, args) => {
const [url, options = {}] = args;
const fullUrl = baseURL + url;
const res = await target(fullUrl, {
...options,
headers: {
...options.headers,
Authorization: "Bearer dev-token"
}
});
if (!res.ok) throw new Error("API Error: " + res.status);
return res.json();
}
});
}
const api = createApi("https://jsonplaceholder.typicode.com");
api("/posts/1").then(console.log);
✅ Use case: API clients with shared headers, retry logic, or error handling.
Wrapping It All Up
The Proxy Pattern in JavaScript is incredibly powerful because it’s built into the language (new Proxy
).
Here are 7 real-world use cases:
- Property logging
- Validation
- Default values
- Access control
- Lazy loading (virtual proxy)
- Caching
- API wrappers
👉 Key takeaway: Proxies let you extend, guard, or virtualize objects without changing their original code, making your architecture cleaner and more flexible.
Call to Action
👉 Which Proxy use case do you find most useful? Drop it in the comments 👇.
📤 Share this with a teammate who still thinks Proxies are just for “tricks.”
🔖 Bookmark this as your Proxy Pattern cheat sheet.
Leave a Reply