Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon

An Introduction to Node.js Design Patterns

Save for later
  • 27 min read
  • 18 Feb 2016

article-image

A design pattern is a reusable solution to a recurring problem; the term is really broad in its definition and can span multiple domains of application. However, the term is often associated with a well-known set of object-oriented patterns that were popularized in the 90's by the book, Design Patterns: Elements of Reusable Object-Oriented Software, Pearson Education by the almost legendary Gang of Four (GoF): Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. We will often refer to these specific set of patterns as traditional design patterns, or GoF design patterns.

(For more resources related to this topic, see here.)

Applying this set of object-oriented design patterns in JavaScript is not as linear and formal as it would happen in a class-based object-oriented language. As we know, JavaScript is multi-paradigm, object-oriented, and prototype-based, and has dynamic typing; it treats functions as first class citizens, and allows functional programming styles. These characteristics make JavaScript a very versatile language, which gives tremendous power to the developer, but at the same time, it causes a fragmentation of programming styles, conventions, techniques, and ultimately the patterns of its ecosystem. There are so many ways to achieve the same result using JavaScript that everybody has their own opinion on what the best way is to approach a problem. A clear demonstration of this phenomenon is the abundance of frameworks and opinionated libraries in the JavaScript ecosystem; probably, no other language has ever seen so many, especially now that Node.js has given new astonishing possibilities to JavaScript and has created so many new scenarios.

In this context, the traditional design patterns too are affected by the nature of JavaScript. There are so many ways in which they can be implemented so that their traditional, strongly object-oriented implementation is not a pattern anymore, and in some cases, not even possible because JavaScript, as we know, doesn't have real classes or abstract interfaces. What doesn't change though, is the original idea at the base of each pattern, the problem it solves, and the concepts at the heart of the solution.

In this article, we will see how some of the most important GoF design patterns apply to Node.js and its philosophy, thus rediscovering their importance from another perspective.

The design patterns explored in this article are as follows:

  • Factory
  • Proxy
  • Decorator
  • Adapter
  • Strategy
  • State
  • Template
  • Middleware
  • Command

This article assumes that the reader has some notion of how inheritance works in JavaScript. Please also be advised that throughout this article we will often use generic and more intuitive diagrams to describe a pattern in place of standard UML, since many patterns can have an implementation based not only on classes, but also on objects and even functions.

Factory

We begin our journey starting from what probably is the most simple and common design pattern in Node.js: Factory.

A generic interface for creating objects

We already stressed the fact that, in JavaScript, the functional paradigm is often preferred to a purely object-oriented design, for its simplicity, usability, and small surface area. This is especially true when creating new object instances. In fact, invoking a factory, instead of directly creating a new object from a prototype using the new operator or Object.create(), is so much more convenient and flexible under several aspects.

First and foremost, a factory allows us to separate the object creation from its implementation; essentially, a factory wraps the creation of a new instance, giving us more flexibility and control in the way we do it. Inside the factory, we can create a new instance leveraging closures, using a prototype and the new operator, using Object.create(), or even returning a different instance based on a particular condition. The consumer of the factory is totally agnostic about how the creation of the instance is carried out. The truth is that, by using new, we are binding our code to one specific way of creating an object, while in JavaScript we can have much more flexibility, almost for free. As a quick example, let's consider a simple factory that creates an Image object:

function createImage(name) {
  return new Image(name);
}
var image = createImage('photo.jpeg');

The createImage() factory might look totally unnecessary, why not instantiate the Image class by using the new operator directly? Something like the following line of code:

var image = new Image(name);

As we already mentioned, using new binds our code to one particular type of object; in the preceding case, to objects of type, Image. A factory instead, gives us much more flexibility; imagine that we want to refactor the Image class, splitting it into smaller classes, one for each image format that we support. If we exposed a factory as the only means to create new images, we can simply rewrite it as follows, without breaking any of the existing code:

function createImage(name) {
  if(name.match(/.jpeg$/)) {
    return new JpegImage(name);
  } else if(name.match(/.gif$/)) {
    return new GifImage(name);
  } else if(name.match(/.png$/)) {
    return new PngImage(name);
  } else {
    throw new Exception('Unsupported format');
  }
}

Our factory also allows us to not expose the constructors of the objects it creates, and prevents them from being extended or modified (remember the principle of small surface area?). In Node.js, this can be achieved by exporting only the factory, while keeping each constructor private.

A mechanism to enforce encapsulation

A factory can also be used as an encapsulation mechanism, thanks to closures.

Encapsulation refers to the technique of controlling the access to some internal details of an object by preventing the external code from manipulating them directly. The interaction with the object happens only through its public interface, isolating the external code from the changes in the implementation details of the object. This practice is also referred to as information hiding. Encapsulation is also a fundamental principle of object-oriented design, together with inheritance, polymorphism, and abstraction.

As we know, in JavaScript, we don't have access level modifiers (for example, we can't declare a private variable), so the only way to enforce encapsulation is through function scopes and closures. A factory makes it straightforward to enforce private variables, consider the following code for example:

function createPerson(name) {
  var privateProperties = {};
  
  var person = {
    setName: function(name) {
      if(!name) throw new Error('A person must have a name');
      privateProperties.name = name;
    },
    getName: function() {
      return privateProperties.name;
    }
  };

  person.setName(name);
  return person;
}

In the preceding code, we leverage closures to create two objects: a person object which represents the public interface returned by the factory and a group of privateProperties that are inaccessible from the outside and that can be manipulated only through the interface provided by the person object. For example, in the preceding code, we make sure that a person's name is never empty, this would not be possible to enforce if name was just a property of the person object.

Factories are only one of the techniques that we have for creating private members; in fact, other possible approaches are as follows:

Defining private variables in a constructor (as recommended by Douglas Crockford: http://javascript.crockford.com/private.html)

Using conventions, for example, prefixing the name of a property with an underscore "_" or the dollar sign "$" (this however, does not technically prevent a member from being accessed from the outside)

Using ES6 WeakMaps (http://fitzgeraldnick.com/weblog/53/)

Building a simple code profiler

Now, let's work on a complete example using a factory. Let's build a simple code profiler, an object with the following properties:

  • A start() method that triggers the start of a profiling session
  • An end() method to terminate the session and log its execution time to the console

Let's start by creating a file named profiler.js, which will have the following content:

function Profiler(label) {
  this.label = label;
  this.lastTime = null;
}

Profiler.prototype.start = function() {
  this.lastTime = process.hrtime();
}

Profiler.prototype.end = function() {
  var diff = process.hrtime(this.lastTime);
  console.log('Timer "' + this.label + '" took '
    + diff[0] + ' seconds and '
    + diff[1] + ' nanoseconds.');
}

There is nothing fancy in the preceding class; we simply use the default high resolution timer to save the current time when start() is invoked, and then calculate the elapsed time when end() is executed, printing the result to the console.

Now, if we are going to use such a profiler in a real-world application to calculate the execution time of the different routines, we can easily imagine the huge amount of logging we will generate to the standard output, especially in a production environment. What we might want to do instead is redirect the profiling information to another source, for example, a database, or alternatively, disabling the profiler altogether if the application is running in the production mode. It's clear that if we were to instantiate a Profiler object directly by using the new operator, we would need some extra logic in the client code or in the Profiler object itself in order to switch between the different logics. We can instead use a factory to abstract the creation of the Profiler object, so that, depending on whether the application runs in the production or development mode, we can return a fully working Profiler object, or alternatively, a mock object with the same interface, but with empty methods. Let's do this then, in the profiler.js module instead of exporting the Profiler constructor, we will export only a function, our factory. The following is its code:

module.exports = function(label) {
  if(process.env.NODE_ENV === 'development') {
    return new Profiler(label);        //[1]
  } else if(process.env.NODE_ENV === 'production') {
    return {             //[2]
      start: function() {},
      end: function() {}
    }
  } else {
    throw new Error('Must set NODE_ENV');
  }
}

The factory that we created abstracts the creation of a profiler object from its implementation:

  • If the application is running in the development mode, we return a new, fully functional Profiler object
  • If instead the application is running in the production mode, we return a mock object where the start() and stop() methods are empty functions

The nice feature to highlight is that, thanks to the JavaScript dynamic typing, we were able to return an object instantiated with the new operator in one circumstance and a simple object literal in the other (this is also known as duck typing http://en.wikipedia.org/wiki/Duck_typing). Our factory is doing its job perfectly; we can really create objects in any way that we like inside the factory function, and we can execute additional initialization steps or return a different type of object based on particular conditions, and all of this while isolating the consumer of the object from all these details. We can easily understand the power of this simple pattern.

Now, we can play with our profiler; this is a possible use case for the factory that we just created earlier:

var profiler = require('./profiler');

function getRandomArray(len) {
  var p = profiler('Generating a ' + len + ' items long array');
  p.start();
  var arr = [];
  for(var i = 0; i < len; i++) {
    arr.push(Math.random());
  }
  p.end();
}

getRandomArray(1e6);
console.log('Done');

The p variable contains the instance of our Profiler object, but we don't know how it's created and what its implementation is at this point in the code.

If we include the preceding code in a file named profilerTest.js, we can easily test these assumptions. To try the program with profiling enabled, run the following command:

export NODE_ENV=development; node profilerTest

The preceding command enables the real profiler and prints the profiling information to the console. If we want to try the mock profiler instead, we can run the following command:

export NODE_ENV=production; node profilerTest

The example that we just presented is just a simple application of the factory function pattern, but it clearly shows the advantages of separating an object's creation from its implementation.

In the wild

As we said, factories are very popular in Node.js. Many packages offer only a factory for creating new instances, some examples are the following:

Other modules expose both a class and a factory, but document the factory as the main method—or the most convenient way—to create new instances; some of the examples are as follows:

  • http-proxy (https://npmjs.org/package/http-proxy): This is a programmable proxying library, where new instances are created with httpProxy.createProxyServer(options).
  • The core Node.js HTTP server: This is where new instances are mostly created using http.createServer(), even though this is essentially a shortcut for new http.Server().
  • bunyan (https://npmjs.org/package/bunyan): This is a popular logging library; in its readme file the contributors propose a factory, bunyan.createLogger(), as the main method to create new instances, even though this would be equivalent to running new bunyan().

Some other modules provide a factory to wrap the creation of other components. Popular examples are through2 and from2 , which allow us to simplify the creation of new streams using a factory approach, freeing the developer from explicitly using inheritance and the new operator.

Proxy

A proxy is an object that controls the access to another object called subject. The proxy and the subject have an identical interface and this allows us to transparently swap one for the other; in fact, the alternative name for this pattern is surrogate. A proxy intercepts all or some of the operations that are meant to be executed on the subject, augmenting or complementing their behavior. The following figure shows the diagrammatic representation:

introduction-nodejs-design-patterns-img-0

The preceding figure shows us how the Proxy and the Subject have the same interface and how this is totally transparent to the client, who can use one or the other interchangeably. The Proxy forwards each operation to the subject, enhancing its behavior with additional preprocessing or post-processing.

It's important to observe that we are not talking about proxying between classes; the Proxy pattern involves wrapping actual instances of the subject, thus preserving its state.

A proxy is useful in several circumstances, for example, consider the following ones:

  • Data validation: The proxy validates the input before forwarding it to the subject
  • Security: The proxy verifies that the client is authorized to perform the operation and it passes the request to the subject only if the outcome of the check is positive
  • Caching: The proxy keeps an internal cache so that the operations are executed on the subject only if the data is not yet present in the cache
  • Lazy initialization: If the creation of the subject is expensive, the proxy can delay it to when it's really necessary
  • Logging: The proxy intercepts the method invocations and the relative parameters, recoding them as they happen
  • Remote objects: A proxy can take an object that is located remotely, and make it appear local

Of course, there are many more applications for the Proxy pattern, but these should give us an idea of the extent of its purpose.

Techniques for implementing proxies

When proxying an object, we can decide to intercept all its methods or only part of them, while delegating the rest of them directly to the subject. There are several ways in which this can be achieved; let's analyze some of them.

Object composition

Composition is the technique whereby an object is combined with another object for the purpose of extending or using its functionality. In the specific case of the Proxy pattern, a new object with the same interface as the subject is created, and a reference to the subject is stored internally in the proxy in the form of an instance variable or a closure variable. The subject can be injected from the client at creation time or created by the proxy itself.

The following is one example of this technique using a pseudo class and a factory:

function createProxy(subject) {
  var proto = Object.getPrototypeOf(subject);
   
  function Proxy(subject) {
    this.subject = subject;
  }
  Proxy.prototype = Object.create(proto);
  
  //proxied method
  Proxy.prototype.hello = function() {
    return this.subject.hello() + ' world!';
  }
  
  //delegated method
  Proxy.prototype.goodbye = function() {
    return this.subject.goodbye
      .apply(this.subject, arguments);
  }
  
  return new Proxy(subject);
}

To implement a proxy using composition, we have to intercept the methods that we are interested in manipulating (such as hello()), while simply delegating the rest of them to the subject (as we did with goodbye()).

The preceding code also shows the particular case where the subject has a prototype and we want to maintain the correct prototype chain, so that, executing proxy instanceof Subject will return true; we used pseudo-classical inheritance to achieve this.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime

This is just an extra step, required only if we are interested in maintaining the prototype chain, which can be useful in order to improve the compatibility of the proxy with code initially meant to work with the subject.

However, as JavaScript has dynamic typing, most of the time we can avoid using inheritance and use more immediate approaches. For example, an alternative implementation of the proxy presented in the preceding code, might just use an object literal and a factory:

function createProxy(subject) {
  return {
    //proxied method
    hello: function() {
      return subject.hello() + ' world!';
    },
  
    //delegated method
    goodbye: function() {
      return subject.goodbye.apply(subject, arguments);
    }
  };
}

If we want to create a proxy that delegates most of its methods, it would be convenient to generate these automatically using a library, such as delegates (https://npmjs.org/package/delegates).

Object augmentation

Object augmentation (or monkey patching)is probably the most pragmatic way of proxying individual methods of an object and consists of modifying the subject directly by replacing a method with its proxied implementation; consider the following example:

function createProxy(subject) {
  var helloOrig = subject.hello;
  subject.hello = function() {
    return helloOrig.call(this) + ' world!';
  }
  
  return subject;
}

This technique is definitely the most convenient one when we need to proxy only one or a few methods, but it has the drawback of modifying the subject object directly.

A comparison of the different techniques

Composition can be considered the safest way of creating a proxy, because it leaves the subject untouched without mutating its original behavior. Its only drawback is that we have to manually delegate all the methods, even if we want to proxy only one of them. If needed, we might also have to delegate the access to the properties of the subject.

The object properties can be delegated using Object.defineProperty(). Find out more at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty.

Object augmentation, on the other hand, modifies the subject, which might not always be what we want, but it does not present the various inconveniences related to delegation. For this reason, object augmentation is definitely the most pragmatic way to implement proxies in JavaScript, and it's the preferred technique in all those circumstances where modifying the subject is not a big concern.

However, there is at least one situation where composition is almost necessary; this is when we want to control the initialization of the subject as for example, to create it only when needed (lazy initialization).

It is worth pointing out that by using a factory function (createProxy() in our examples), we can shield our code from the technique used to generate the proxy.

Creating a logging Writable stream

To see the proxy pattern in a real example, we will now build an object that acts as a proxy to a Writable stream, by intercepting all the calls to the write() method and logging a message every time this happens. We will use an object composition to implement our proxy; this is how the loggingWritable.js file looks:

function createLoggingWritable(writableOrig) {
  var proto = Object.getPrototypeOf(writableOrig);
  
  function LoggingWritable(subject) {
    this.writableOrig = writableOrig;
  }
  LoggingWritable.prototype = Object.create(proto);
  
  LoggingWritable.prototype.write =
    function(chunk, encoding, callback) {
      if(!callback && typeof encoding === 'function') {
        callback = encoding;
        encoding = undefined;
      }
      console.log('Writing ', chunk);
      return this.writableOrig.write(chunk, encoding, function() {
        console.log('Finished writing ', chunk);
        callback && callback();
      });
    };
  
  LoggingWritable.prototype.on = function() {
    return this.writableOrig.on
      .apply(this.writableOrig, arguments);
  };
  
  LoggingWritable.prototype.end = function() {
    return this.writableOrig.end
      .apply(this.writableOrig, arguments);
  }
  
  return new LoggingWritable(this.writableOrig);
}

In the preceding code, we created a factory that returns a proxied version of the writable object passed as an argument. We provide an override for the write() method that logs a message to the standard output every time it is invoked and every time the asynchronous operation completes. This is also a good example to demonstrate the particular case of creating proxies of asynchronous functions, which makes necessary to proxy the callback as well; this is an important detail to be considered in a platform such as Node.js. The remaining methods, on() and end(), are simply delegated to the original writable stream (to keep the code leaner we are not considering the other methods of the Writable interface).

We can now include a few more lines of code into the logginWritable.js module to test the proxy that we just created:

var fs = require('fs');
var writable = fs.createWriteStream('test.txt');
var writableProxy = createProxy(writable);
writableProxy.write('First chunk');
writableProxy.write('Second chunk');
writable.write('This is not logged');
writableProxy.end();

The proxy did not change the original interface of the stream or its external behavior, but if we run the preceding code, we will now see that every chunk that is written into the stream is transparently logged to the console.

Proxy in the ecosystem – function hooks and AOP

In its numerous forms, Proxy is a quite popular pattern in Node.js and in the ecosystem. In fact, we can find several libraries that allow us to simplify the creation of proxies, most of the time leveraging object augmentation as an implementation approach. In the community, this pattern can be also referred to as function hooking or sometimes also as Aspect Oriented Programming (AOP), which is actually a common area of application for proxies. As it happens in AOP, these libraries usually allow the developer to set pre or post execution hooks for a specific method (or a set of methods) that allow us to execute some custom code before and after the execution of the advised method respectively.

Sometimes proxies are also called middleware, because, as it happens in the middleware pattern, they allow us to preprocess and post-process the input/output of a function. Sometimes, they also allow to register multiple hooks for the same method using a middleware-like pipeline.

There are several libraries on npm that allow us to implement function hooks with little effort. Among them there are hooks (https://npmjs.org/package/hooks), hooker (https://npmjs.org/package/hooker), and meld (https://npmjs.org/package/meld).

In the wild

Mongoose (http://mongoosejs.com) is a popular Object-Document Mapping (ODM) library for MongoDB. Internally, it uses the hooks package (https://npmjs.org/package/hooks) to provide pre and post execution hooks for the init, validate, save, and remove methods of its Document objects. Find out more on the official documentation at http://mongoosejs.com/docs/middleware.html.

Decorator

Decorator is a structural pattern that consists of dynamically augmenting the behavior of an existing object. It's different from classical inheritance, because the behavior is not added to all the objects of the same class but only to the instances that are explicitly decorated.

Implementation-wise, it is very similar to the Proxy pattern, but instead of enhancing or modifying the behavior of the existing interface of an object, it augments it with new functionalities, as described in the following figure:

introduction-nodejs-design-patterns-img-1

In the previous figure, the Decorator object is extending the Component object by adding the methodC() operation. The existing methods are usually delegated to the decorated object, without further processing. Of course, if necessary we can easily combine the Proxy pattern, so that also the calls to the existing methods can be intercepted and manipulated.

Techniques for implementing decorators

Although Proxy and Decorator are conceptually two different patterns, with different intents, they practically share the same implementation strategies. Let's revise them.

Composition

Using composition, the decorated component is wrapped around a new object that usually inherits from it. The Decorator in this case simply needs to define the new methods while delegating the existing ones to the original component:

function decorate(component) {
  var proto = Object.getPrototypeOf(component);

  function Decorator(component) {
    this.component = component;
  }
  Decorator.prototype = Object.create(proto);
  //new method
  Decorator.prototype.greetings = function() {
    //...
  };
  
  //delegated method
  Decorator.prototype.hello = function() {
    this.component.hello.apply(this.component, arguments);
  };
  
  return new Decorator(component);
}

Object augmentation

Object decoration can also be achieved by simply attaching new methods directly to the decorated object, as follows:

function decorate(component) {
  //new method
  component.greetings = function() {
    //...
  };
  return component;
}

The same caveats discussed during the analysis of the Proxy pattern are also valid for Decorator. Let's now practice the pattern with a working example!

Decorating a LevelUP database

Before we start coding with the next example, let's spend a few words to introduce LevelUP, the module that we are now going to work with.

Introducing LevelUP and LevelDB

LevelUP (https://npmjs.org/package/levelup) is a Node.js wrapper around Google's LevelDB, a key-value store originally built to implement IndexedDB in the Chrome browser, but it's much more than that. LevelDB has been defined by Dominic Tarr as the "Node.js of databases", because of its minimalism and extensibility. Like Node.js, LevelDB provides blazing fast performances and only the most basic set of features, allowing developers to build any kind of database on top of it.

The Node.js community, and in this case Rod Vagg, did not miss the chance to bring the power of this database into Node.js by creating LevelUP. Born as a wrapper for LevelDB, it then evolved to support several kinds of backends, from in-memory stores, to other NoSQL databases such as Riak and Redis, to web storage engines such as IndexedDB and localStorage, allowing to use the same API on both the server and the client, opening up some really interesting scenarios.

Today, there is a full-fledged ecosystem around LevelUp made of plugins and modules that extend the tiny core to implement features such as replication, secondary indexes, live updates, query engines, and more. Also, complete databases were built on top of LevelUP, including CouchDB clones such as PouchDB (https://npmjs.org/package/pouchdb) and CouchUP (https://npmjs.org/package/couchup), and even a graph database, levelgraph (https://npmjs.org/package/levelgraph) that can work both on Node.js and the browser!

Find out more about the LevelUP ecosystem at https://github.com/rvagg/node-levelup/wiki/Modules.

Implementing a LevelUP plugin

In the next example, we are going to show how we can create a simple plugin for LevelUp using the Decorator pattern, and in particular, the object augmentation technique, which is the simplest but nonetheless the most pragmatic and effective way to decorate objects with additional capabilities.

For convenience, we are going to use the level package (http://npmjs.org/package/level) that bundles both levelup and the default adapter called leveldown, which uses LevelDB as the backend.

What we want to build is a plugin for LevelUP that allows to receive notifications every time an object with a certain pattern is saved into the database. For example, if we subscribe to a pattern such as {a: 1}, we want to receive a notification when objects such as {a: 1, b: 3} or {a: 1, c: 'x'} are saved into the database.

Let's start to build our small plugin by creating a new module called levelSubscribe.js. We will then insert the following code:

module.exports = function levelSubscribe(db) {

  db.subscribe = function(pattern, listener) {       //[1]
    db.on('put', function(key, val) {         //[2]
      var match = Object.keys(pattern).every(function(k) { //[3]
        return pattern[k] === val[k];
      });
      if(match) {
        listener(key, val);            //[4]
      }
    });
  };

  return db;
}

That's it for our plugin, and it's extremely simple. Let's see what happens in the preceding code briefly:

  1. We decorated the db object with a new method named subscribe(). We simply attached the method directly to the provided db instance (object augmentation).
  2. We listen for any put operation performed on the database.
  3. We performed a very simple pattern-matching algorithm, which verified that all the properties in the provided pattern are also available on the data being inserted.
  4. If we have a match, we notify the listener.

Let's now create some code—in a new file named levelSubscribeTest.js—to try out our new plugin:

var level = require('level');           //[1]
var db = level(__dirname + '/db', {valueEncoding: 'json'});

var levelSubscribe = require('./levelSubscribe');     //[2]
db = levelSubscribe(db);

db.subscribe({doctype: 'tweet', language: 'en'},     //[3]
  function(k, val){
    console.log(val);
  });
                  //[4]
db.put('1', {doctype: 'tweet', text: 'Hi', language: 'en'});
db.put('2', {doctype: 'company', name: 'ACME Co.'});

This is what we did in the preceding code:

  1. First, we initialize our LevelUP database, choosing the directory where the files will be stored and the default encoding for the values.
  2. Then, we attach our plugin, which decorates the original db object.
  3. At this point, we are ready to use the new feature provided by our plugin, the subscribe() method, where we specify that we are interested in all the objects with doctype: 'tweet' and language: 'en'.
  4. Finally, we save some values in the database, so that we can see our plugin in action:
    db.put('1', {doctype: 'tweet', text: 'Hi', language: 'en'});
    db.put('2', {doctype: 'company', name: 'ACME Co.'});

This example shows a real application of the decorator pattern in its most simple implementation: object augmentation. It might look like a trivial pattern but it has undoubted power if used appropriately.

For simplicity, our plugin will work only in combination with the put operations, but it can be easily expanded to work even with the batch operations (https://github.com/rvagg/node-levelup#batch).

In the wild

For more examples of how Decorator is used in the real world, we might want to inspect the code of some more LevelUp plugins:

Summary

To learn more about Node.js, the following books published by Packt Publishing (https://www.packtpub.com/) are recommended:

Resources for Article:


Further resources on this subject: