JavaScript Proxy API: Practical Patterns for Real-World Use
Master the JavaScript Proxy API with practical patterns including validation, logging, caching, and reactive objects.
What is the Proxy API?
The Proxy API lets you intercept and customize fundamental operations on objects — property access, assignment, function invocation, and more. Unlike simple wrappers, Proxy gives you fine-grained control over how objects behave, opening up patterns that are impossible with plain JavaScript objects.
A Proxy wraps a target object and defines "traps" — handler functions that intercept operations. The beauty is that the proxy is transparent: code using the proxy doesn't know it's not the original object.
Validation Proxy
One of the most practical uses is automatic input validation. Instead of scattering validation logic across your codebase, you can centralize it in a proxy.
``javascript
function createValidatedObject(schema) {
return new Proxy({}, {
set(target, prop, value) {
const validator = schema[prop];
if (validator && !validator(value)) {
throw new TypeError(\Invalid value for \${String(prop)}: \${value}\);
}
target[prop] = value;
return true;
}
});
}
const user = createValidatedObject({ age: (v) => typeof v === 'number' && v >= 0 && v <= 150, email: (v) => typeof v === 'string' && v.includes('@'), name: (v) => typeof v === 'string' && v.length > 0 });
user.name = 'Alice'; // OK
user.age = 25; // OK
user.age = -5; // TypeError: Invalid value for age: -5
`
Observable / Reactive Proxy
You can build a simple reactive system that triggers callbacks whenever data changes — the same idea behind Vue 3's reactivity.
`javascript
function reactive(target, onChange) {
return new Proxy(target, {
set(obj, prop, value) {
const oldValue = obj[prop];
obj[prop] = value;
if (oldValue !== value) {
onChange(prop, value, oldValue);
}
return true;
},
deleteProperty(obj, prop) {
const oldValue = obj[prop];
delete obj[prop];
onChange(prop, undefined, oldValue);
return true;
}
});
}
const state = reactive({ count: 0 }, (prop, newVal, oldVal) => {
console.log(\\${String(prop)} changed: \${oldVal} → \${newVal}\);
});
state.count = 1; // "count changed: 0 → 1"
`
Caching and Memoization Proxy
Wrap expensive function calls with a proxy that caches results automatically.
`javascript
function memoize(fn) {
const cache = new Map();
return new Proxy(fn, {
apply(target, thisArg, args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = Reflect.apply(target, thisArg, args);
cache.set(key, result);
return result;
}
});
}
const expensiveCalc = memoize((n) => { console.log('Computing...'); return n * n; });
expensiveCalc(5); // "Computing..." → 25 expensiveCalc(5); // 25 (cached, no log) ``
These patterns show that Proxy is not just an academic API — it solves real problems elegantly and can replace entire libraries when used thoughtfully.