Skip to content

Proxy

This lesson explains Proxy with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.

ES6+ Proxy Overview

Proxy is a built-in ES6 feature that lets you wrap an object and intercept fundamental operations such as property reads, writes, deletion, and function calls. You define custom behavior through a handler object containing trap functions.

Proxy is powerful for validation, logging, access control, reactive state, and metaprogramming. It is commonly paired with Reflect, whose methods mirror Proxy trap names and provide default forwarding behavior.

Feature Description
Introduced In ES6 (ECMAScript 2015)
Syntax new Proxy(target, handler)
Target The original object being wrapped
Handler Object with trap functions
Common Traps get, set, has, deleteProperty
Typical Uses Validation, logging, reactive data, API wrappers

Basic Proxy Example

const user = {
  name: "Alex",
  role: "admin"
};

const handler = {
  get(target, key) {
    console.log(
      `Reading property: ${String(key)}`
    );
    return target[key];
  },

  set(target, key, value) {
    console.log(
      `Setting ${String(key)} to ${value}`
    );
    target[key] = value;
    return true;
  }
};

const proxy =
  new Proxy(user, handler);

console.log(proxy.name);
proxy.role = "editor";

A Proxy wraps the original user object. Every property read and write passes through the handler traps before reaching the target.

How Proxy Works

When code interacts with a proxy, JavaScript calls the matching trap in the handler. If no trap exists, the default behavior runs on the target object.

Step Description Example
Create Target Start with a normal object or function. const data = { ... }
Define Handler Add trap functions for operations to intercept. { get() { ... } }
Wrap with Proxy Create the proxy instance. new Proxy(data, handler)
Use Proxy Access properties through the proxy. proxy.title
Trap Runs Handler logic executes before target access. get(target, key)

Common Proxy Traps

Trap Intercepts Reflect Equivalent
get Property read Reflect.get()
set Property assignment Reflect.set()
has in operator Reflect.has()
deleteProperty delete operator Reflect.deleteProperty()
ownKeys Object.keys() and related APIs Reflect.ownKeys()
apply Function call Reflect.apply()
construct new operator Reflect.construct()

Validation Proxy Example

function createUser(data) {
  return new Proxy(data, {
    set(target, key, value) {
      if (
        key === "age" &&
        typeof value !== "number"
      ) {
        throw new TypeError(
          "Age must be a number"
        );
      }

      if (
        key === "email" &&
        !String(value).includes("@")
      ) {
        throw new TypeError(
          "Email must be valid"
        );
      }

      return Reflect.set(
        target,
        key,
        value
      );
    }
  });
}

const user =
  createUser({
    name: "Sam",
    age: 25,
    email: "sam@example.com"
  });

user.age = 26;
console.log(user);

Proxy traps are ideal for validating data before it is stored on an object. Invalid values can throw errors or be rejected.

Default Values with get Trap

const defaults = {
  theme: "light",
  language: "en"
};

const settings =
  new Proxy(defaults, {
    get(target, key) {
      if (key in target) {
        return Reflect.get(
          target,
          key
        );
      }

      return undefined;
    }
  });

console.log(settings.theme);
console.log(settings.language);
console.log(settings.timezone);

The get trap can return fallback values, hide sensitive fields, or transform data before it reaches the caller.

Controlling Property Visibility

const profile = {
  name: "Alex",
  email: "alex@example.com",
  password: "secret123"
};

const safeProfile =
  new Proxy(profile, {
    has(target, key) {
      if (key === "password") {
        return false;
      }

      return Reflect.has(
        target,
        key
      );
    },

    get(target, key) {
      if (key === "password") {
        return undefined;
      }

      return Reflect.get(
        target,
        key
      );
    }
  });

console.log(
  "password" in safeProfile
);
console.log(safeProfile.name);

Use has and get traps together to hide internal or sensitive properties from consumers of an object.

Blocking Property Deletion

const config = {
  apiUrl: "https://api.example.com",
  version: "1.0.0"
};

const lockedConfig =
  new Proxy(config, {
    deleteProperty(target, key) {
      if (key === "apiUrl") {
        console.log(
          "Cannot delete apiUrl"
        );
        return false;
      }

      return Reflect.deleteProperty(
        target,
        key
      );
    }
  });

delete lockedConfig.version;
delete lockedConfig.apiUrl;

console.log(lockedConfig);

The deleteProperty trap lets you prevent removal of critical configuration keys while allowing other properties to be deleted normally.

Proxy for Functions

function add(a, b) {
  return a + b;
}

const tracedAdd =
  new Proxy(add, {
    apply(target, thisArg, args) {
      console.log(
        "Calling add with",
        args
      );

      return Reflect.apply(
        target,
        thisArg,
        args
      );
    }
  });

console.log(tracedAdd(4, 6));

Functions can also be proxied. The apply trap intercepts function calls, which is useful for tracing, timing, or middleware patterns.

Forwarding with Reflect

const product = {
  title: "Monitor",
  price: 299
};

const handler = {
  get(target, key, receiver) {
    return Reflect.get(
      target,
      key,
      receiver
    );
  },

  set(target, key, value, receiver) {
    return Reflect.set(
      target,
      key,
      value,
      receiver
    );
  }
};

const proxy =
  new Proxy(product, handler);

proxy.price = 279;
console.log(proxy.price);

Forwarding traps to Reflect preserves default object behavior while giving you a clean place to add custom logic around it.

Revocable Proxy

const target = {
  status: "active"
};

const handler = {
  get(obj, key) {
    return obj[key];
  }
};

const { proxy, revoke } =
  Proxy.revocable(
    target,
    handler
  );

console.log(proxy.status);

revoke();

try {
  console.log(proxy.status);
} catch (error) {
  console.log(error.message);
}

Proxy.revocable() creates a proxy that can be disabled later. After revocation, any operation on the proxy throws a TypeError.

Simple Reactive State Pattern

function createReactiveState(
  initialState,
  onChange
) {
  return new Proxy(initialState, {
    set(target, key, value) {
      const previous =
        target[key];

      const result =
        Reflect.set(
          target,
          key,
          value
        );

      if (previous !== value) {
        onChange(key, value, previous);
      }

      return result;
    }
  });
}

const state =
  createReactiveState(
    { count: 0 },
    (key, value) => {
      console.log(
        `${key} changed to ${value}`
      );
    }
  );

state.count = 1;
state.count = 2;

Proxy is a foundation for reactive systems. The set trap detects changes and runs callbacks when state updates.

Common Proxy Use Cases

  • Validating object properties before assignment.
  • Logging reads and writes for debugging.
  • Hiding sensitive or internal fields.
  • Building reactive state and data-binding utilities.
  • Creating read-only or partially mutable objects.
  • Wrapping APIs with access control or rate limiting.
  • Implementing virtual or computed properties.

Proxy Best Practices

  • Forward unhandled behavior to Reflect methods.
  • Always return the correct boolean from set and deleteProperty traps.
  • Keep trap logic focused on one responsibility.
  • Use Proxy for cross-cutting behavior, not everyday property access.
  • Document proxy behavior clearly for other developers.
  • Test edge cases such as symbol keys and inherited properties.
  • Consider Proxy.revocable() when access should expire.

Common Proxy Mistakes

  • Forgetting to return true or Reflect.set(...) from a set trap.
  • Using Proxy where simpler validation functions would be clearer.
  • Assuming Proxy intercepts all JavaScript object behavior.
  • Mutating the target directly and bypassing the proxy wrapper.
  • Not handling symbol property keys in traps.
  • Creating deeply nested proxies without a clear purpose.
  • Expecting Proxy to work on primitive values without wrapping them in objects.

Proxy vs Reflect

Feature Proxy Reflect
Role Intercepts operations on a target. Performs default object operations.
Syntax new Proxy(target, handler) Reflect.get(...)
Customization High — define trap behavior. Low — standard behavior only.
Together Provides interception layer. Used to forward default behavior.

Key Takeaways

  • Proxy wraps an object and intercepts operations through handler traps.
  • Common traps include get, set, has, and deleteProperty.
  • Use Proxy for validation, logging, access control, and reactive patterns.
  • Pair Proxy with Reflect to preserve default object behavior.
  • Return correct values from traps, especially in set handlers.

Pro Tip

Start with one trap such as set for validation, forward everything else to Reflect, and only add more traps when you have a clear need.