Sometimes we may want to access indirectly to an object, via a substitute. That substitute is called Proxy. The Proxy Pattern helps us deal with a proxy object and talk to the actual object. In this tutorial, we’re gonna look at 2 ways to implement Proxy Pattern in Node.js:
- Using custom Proxy Class
- Using Proxy API
Contents
Proxy Pattern Overview
The image above shows that Proxy
and Subject
have the same methods. Proxy
forwards each operation to Subject
, we can improve Subject
‘s methods with additional pre-processing or post-processing.
Proxy Pattern in Node.js using custom Class
Create Subject class
We create a Bank
class with 3 methods:
– deposit()
increases cash.
– withdraw()
decreases cash.
– total()
returns cash.
Bank.js
class Bank { constructor() { this.cash = 0; } deposit(amount) { this.cash += amount; return this.cash; } withdraw(amount) { if (amount <= this.cash) { this.cash -= amount; return true; } else { return false; } } total() { return this.cash; } } module.exports = Bank; |
Create custom Proxy class
The Proxy
class also have 3 methods: deposit()
, withdraw()
, total()
. Inside each method, we call Subject
‘s methods with additional process.
Notice that we need initiate Bank
object in constructor()
method.
BankProxy.js
const Bank = require('./Bank'); class BankProxy { constructor() { this.bank = new Bank(); } deposit(amount) { this.bank.deposit(amount); console.log(`deposit ${amount}... total cash: ${this.bank.total()}`); } withdraw(amount) { if (this.bank.withdraw(amount)) { console.log(`withdraw ${amount}... total cash: ${this.bank.total()}`); } else { console.log(`failed to withdraw!`); } } total() { return console.log(`total cash: ${this.bank.total()}`); } } module.exports = BankProxy; |
Create Client
app.js
const BankProxy = require('./BankProxy'); const bankProxy = new BankProxy(); bankProxy.deposit(4000); bankProxy.withdraw(500); bankProxy.deposit(300); bankProxy.total(); |
Run with command: node app.js
. Here is the result:
deposit 4000... total cash: 4000 withdraw 500... total cash: 3500 deposit 300... total cash: 3800 total cash: 3800 |
Proxy Pattern in Node.js using Proxy API
Instead of creating our own custom Proxy class, we can use Proxy
API that returns a proxy of the object.
Syntax
var p = new Proxy(target, handler); |
target
: the object that Proxy will wrap. It can be a native array, a function or even another proxy.handler
: the object whose properties are functions (get
,set
for examples) which define the behavior of theProxy
.
Use Proxy API to create Proxy objects
We create module call proxy
with 2 functions:
– encrypt
: returns Proxy
object that encrypts properties’ values.
– decrypt
: returns Proxy
object that decrypts properties’ values.
We use getter function get
and setter function set
for handler
parameter.
proxy.js
const Cryptr = require('cryptr'); const encrypt = (obj, secretKey) => { const cryptr = new Cryptr(secretKey); let encObj = {}; for (const prop of Object.keys(obj)) { encObj[prop] = cryptr.encrypt(obj[prop]); } return new Proxy(encObj, { set(encObj, prop, value) { return (encObj[prop] = cryptr.encrypt(value)); }, get(encObj, prop) { return encObj[prop]; } }); } const decrypt = (obj, secretKey) => { const cryptr = new Cryptr(secretKey); let decObj = {}; for (const prop of Object.keys(obj)) { decObj[prop] = cryptr.decrypt(obj[prop]); } return new Proxy(decObj, { set() { throw new Error('This is a read-only object'); }, get(decObj, prop) { return decObj[prop]; } }); } module.exports = { encrypt, decrypt } |
Use Proxy objects
app.js
const { encrypt, decrypt } = require('./proxy'); const secretKey = 'grokonez.com'; const obj = { title: 'grokonez', age: 2, domain: 'grokonez.com', country: 'US' }; const encObj = encrypt(obj, secretKey); console.log(encObj); /** { title: '2b6e0af648eb4ea0aadc8e7f6a101e3830eae133c3ee9545', age: 'dfc19adaa87b121e94efe70944afd6811d', domain: 'b1d6dc68c403d9054d8618eb429460850797788019654dee5f89d920', country: 'ebfde53088929f74360fe8f5523c8074af1d' } */ encObj.description = 'Programming Tutorials'; console.log(encObj); /** { title: '2b6e0af648eb4ea0aadc8e7f6a101e3830eae133c3ee9545', age: 'dfc19adaa87b121e94efe70944afd6811d', domain: 'b1d6dc68c403d9054d8618eb429460850797788019654dee5f89d920', country: 'ebfde53088929f74360fe8f5523c8074af1d', description: 'ef56fa3ee340b6cd2881724c9ac2038e4323defe10cb02d19b003a6433ed012b82296d1bbf' } */ const decObj = decrypt(encObj, secretKey); console.log(decObj); /** { title: 'grokonez', age: '2', domain: 'grokonez.com', country: 'US', description: 'Programming Tutorials' } */ console.log(`get domain: ${decObj.domain}`); /** get domain: grokonez.com */ decObj.domain = 'crawl.com'; /** throw new Error('This is a read-only object'); ^ Error: This is a read - only object */ |
To run this Nodejs example, we need install cryptr
first with command: npm install cryptr
.
Proxy that generates Observable object
We can create Proxy
with function that executes every time a given object is modified.
const obj = { title: 'grokonez', age: 2, domain: 'grokonez.com', country: 'US' }; const react = (obj, observer) => { return new Proxy(obj, { set(target, prop, value) { observer({ [prop]: value }); return (target[prop] = value); } }); } const reactive = react(obj, res => console.log(res)); reactive.age = 3; /** { age: 3 } */ reactive.description = 'Programming Tutorials'; /** { description: 'Programming Tutorials' } */ console.log(obj); /** { title: 'grokonez', age: 3, domain: 'grokonez.com', country: 'US', description: 'Programming Tutorials' } */ |
Proxy API traps
What is a trap?
=> A trap is a method that provides property access.
So, get
& set
function in the example above are traps, we have some more traps:
– getPrototypeOf()
– setPrototypeOf()
– isExtensible()
– preventExtensions()
– getOwnPropertyDescriptor()
– defineProperty()
– has()
– deleteProperty()
– ownKeys()
– apply()
– construct()
You can find details at: Proxy#Methods_of_the_handler_object.
Some notes
– Proxy pattern helps us create wrapper for object that allows external access control to the object.
– Proxy API (ES2015, NodeJs v6+) has Proxy
API that enables creation of proxy.
– Proxy implements various useful traps for property access.
– This pattern is good for implementing middleware mechanism for caching, logging, encryption or other kinds of augmented functionality.