JavaScript Proxy Pattern: Control Object Access
The Proxy pattern in JavaScript allows developers to place an
intermediary layer between an application and a target object to
intercept, redefine, and control fundamental operations. By wrapping an
original object inside a Proxy instance configured with a
handler, you can govern property access, block unauthorized
mutations, validate incoming data, hide private attributes, and even
revoke access dynamically. This article explains how the JavaScript
Proxy object works, explores the essential traps used for
access control, and demonstrates practical implementations for securing
and managing data flow.
Understanding the Core Components: Target and Handler
A JavaScript Proxy consists of three main elements: the target object, the handler object, and the proxy instance itself. The syntax is:
const proxy = new Proxy(target, handler);- Target: The original object, array, or function being wrapped.
- Handler: An object containing methods (known as “traps”) that intercept operations performed on the proxy.
- Traps: Functions within the handler that execute in place of standard operations such as reading properties, assigning values, or deleting keys.
When an operation is executed on the proxy, JavaScript checks the handler for a matching trap. If the trap is defined, it runs; otherwise, the operation falls back directly to the target object.
Intercepting Operations with Traps
Access control relies on specific handler traps that intercept attempts to interact with the underlying data.
1. The get
Trap: Regulating Read Access
The get trap executes whenever a property is read. It
receives the target, the property being
accessed, and the receiver (the proxy instance).
You can use the get trap to hide sensitive or private
properties, redirect queries, or throw errors when unauthorized keys are
requested:
const user = {
id: 101,
username: "johndoe",
apiKey: "secret_live_98765"
};
const userProxy = new Proxy(user, {
get(target, prop) {
if (prop === "apiKey") {
throw new Error("Access denied: Property is private.");
}
return prop in target ? target[prop] : undefined;
}
});
console.log(userProxy.username); // "johndoe"
console.log(userProxy.apiKey); // Throws Error: Access denied2. The
set Trap: Validating and Restricting Write Access
The set trap intercepts attempts to modify an existing
property or add a new one. It receives the target,
property, value, and receiver.
The trap must return true to indicate success or
false/an error to reject the change.
This trap is commonly used for data validation and creating read-only objects:
const account = {
balance: 500
};
const accountProxy = new Proxy(account, {
set(target, prop, value) {
if (prop === "balance") {
if (typeof value !== "number" || value < 0) {
throw new TypeError("Balance must be a positive number.");
}
}
target[prop] = value;
return true;
}
});
accountProxy.balance = 750; // Allowed
accountProxy.balance = -50; // Throws TypeError3. The has
Trap: Masking Property Existence
The has trap intercepts the in operator.
This prevents external code from discovering whether a specific key
exists on an object:
const profile = {
name: "Alice",
_internalId: "A-992"
};
const secureProfile = new Proxy(profile, {
has(target, prop) {
if (prop.startsWith("_")) {
return false;
}
return prop in target;
}
});
console.log("name" in secureProfile); // true
console.log("_internalId" in secureProfile); // false4. The
deleteProperty Trap: Preventing Key Deletion
The deleteProperty trap intercepts the
delete operator, protecting critical keys from removal:
const config = {
environment: "production",
port: 8080
};
const safeConfig = new Proxy(config, {
deleteProperty(target, prop) {
if (prop === "environment") {
throw new Error(`Cannot delete immutable property: ${prop}`);
}
delete target[prop];
return true;
}
});
delete safeConfig.port; // Allowed
delete safeConfig.environment; // Throws ErrorUsing
Proxy.revocable for Temporary Access
JavaScript provides Proxy.revocable(target, handler),
which returns both the proxy instance and a revoke
function. Once revoke() is called, all further operations
on the proxy immediately throw a TypeError.
This is useful for granting time-limited access or safely passing sensitive objects to third-party modules:
const sensitiveData = { token: "abc-123" };
const { proxy, revoke } = Proxy.revocable(sensitiveData, {
get(target, prop) {
return target[prop];
}
});
console.log(proxy.token); // "abc-123"
// Revoke access
revoke();
console.log(proxy.token); // Throws TypeError: Cannot perform 'get' on a proxy that has been revokedUsing Reflect with
Proxies
When implementing traps, standard access control patterns use the
built-in Reflect API. Reflect methods mirror
the proxy traps and handle the default object behavior cleanly while
preserving correct this binding through the
receiver argument:
const handler = {
set(target, prop, value, receiver) {
if (typeof value === "string") {
value = value.trim();
}
return Reflect.set(target, prop, value, receiver);
}
};Summary of Use Cases for Access Control
- Data Validation: Enforce schemas and types before writing to properties.
- Immutability: Block
setanddeletePropertyoperations to build deeply frozen or read-only states. - Information Hiding: Use
getandhastraps to make private fields invisible and inaccessible. - Audit and Logging: Track read and write attempts to monitor security and usage across modules.
- Temporary Permissions: Use
Proxy.revocableto cleanly sever access to an object when a task completes.