Design patterns in Node.js: a practical guide

Image for post
Image for post

What are design patterns?

Design patterns, simply put, are a way for you to structure your solution’s code in a way that allows you to gain some kind of benefit. Such as faster development speed, code reusability, and so on.

Immediately Invoked Function Expressions (IIFE)

The first pattern I’m going to show you is one that allows you to define and call a function at the same time. Due to the way JavaScript scopes works, using IIFEs can be great to simulate things like private properties in classes. In fact, this particular pattern is sometimes used as part of the requirements of other, more complex ones. We’ll see how in a bit.

What does an IIFE look like?

But before we delve into the use cases and the mechanics behind it, let me quickly show you what it looks like exactly:

(function() {
var x = 20;
var y = 20;
var answer = x + y;
console.log(answer);
})();
(function(/*received parameters*/) {
//your code here
})(/*parameters*/)

Use cases

Although it might sound crazy, there are actually a few benefits and use cases where using an IIFE can be a good thing, for example:

Simulating static variables

Remember static variables? From other languages such as C or C# for example. If you’re not familiar with them, a static variable gets initialized the first time you use it, and then it takes the value that you last set it to. The benefit being that if you define a static variable inside a function, that variable will be common to all instances of the function, no matter how many times you call it, so it greatly simplifies cases like this:

function autoIncrement() {
static let number = 0
number++
return number
}
let autoIncrement = (function() {
let number = 0
return function () {
number++
return number
}
})()

Simulating private variables

As you may (or may not, I guess) already know, ES6 classes treat every member as public, meaning, there are no private properties or methods. That’s out of the question, but thanks to IIFEs you could potentially simulate that if you wanted to.

const autoIncrementer = (function() {
let value = 0;
return {
incr() {
value++
},
get value() {
return value
}
};
})();
> autoIncrementer.incr()
undefined
> autoIncrementer.incr()
undefined
> autoIncrementer.value
2
> autoIncrementer.value = 3
3
> autoIncrementer.value
2

Factory method pattern

This one, in particular, is one of my favourite patterns, since it acts as a tool you can implement to clean your code up a bit.

What does the factory method pattern look like?

This particular pattern would be easier to understand if you first look at its usage, and then at its implementation.

( _ => {    let factory = new MyEmployeeFactory()    let types = ["fulltime", "parttime", "contractor"]
let employees = [];
for(let i = 0; i < 100; i++) {
employees.push(factory.createEmployee({type: types[Math.floor( (Math.random(2) * 2) )]}) )}

//....
employees.forEach( e => {
console.log(e.speak())
})
})()
class Employee {    speak() {
return "Hi, I'm a " + this.type + " employee"
}
}class FullTimeEmployee extends Employee{
constructor(data) {
super()
this.type = "full time"
//....
}
}
class PartTimeEmployee extends Employee{
constructor(data) {
super()
this.type = "part time"
//....
}
}
class ContractorEmployee extends Employee{
constructor(data) {
super()
this.type = "contractor"
//....
}
}
class MyEmployeeFactory { createEmployee(data) {
if(data.type == 'fulltime') return new FullTimeEmployee(data)
if(data.type == 'parttime') return new PartTimeEmployee(data)
if(data.type == 'contractor') return new ContractorEmployee(data)
}
}

Use case

The previous code already shows a generic use case, but if we wanted to be more specific, one particular use case I like to use this pattern for is handling error object creation.

if(err) {
res.json({error: true, message: “Error message here”})
}
if(err) {
res.json(ErrorFactory.getError(err))
}

Singleton pattern

This one is another oldie but a goodie. It’s quite a simple pattern, mind you, but it helps you keep track of how many instances of a class you’re instantiating. Actually, it helps you keep that number to just one, all of the time. Mainly, the singleton pattern, allows you to instantiate an object once, and then use that one every time you need it, instead of creating a new one without having to keep track of a reference to it, either globally or just passing it as a dependency everywhere.

What does the singleton pattern look like?

Normally, other languages implement this pattern using a single static property where they store the instance once it exists. The problem here is that, as I mentioned before, we don’t have access to static variables in JS. So we could implement this in two ways, one would be by using IIFEs instead of classes.

let instance = nullclass SingletonClass {    constructor() {
this.value = Math.random(100)
}
printValue() {
console.log(this.value)
}
static getInstance() {
if(!instance) {
instance = new SingletonClass()
}
return instance
}
}
module.exports = SingletonClass
const obj = Singleton.getInstance()
const obj2 = Singleton.getInstance()
obj.printValue()
obj2.printValue()
console.log("Equals:: ", obj === obj2)
0.5035326348000628
0.5035326348000628
Equals:: true

Use cases

When trying to decide if you need a singleton-like implementation or not, you need to consider something: how many instances of your classes will you really need? If the answer is 2 or more, then this is not your pattern.

const driver = require("...")let instance = null
class DBClass { constructor(props) {
this.properties = props
this._conn = null
}
connect() {
this._conn = driver.connect(this.props)
}
get conn() {
return this._conn
}
static getInstance() {
if(!instance) {
instance = new DBClass()
}
return instance
}
}
module.exports = DBClass

Observer pattern

This one is a very interesting pattern, in the sense that it allows you to respond to certain input by being reactive to it, instead of proactively checking if the input is provided. In other words, with this pattern, you can specify what kind of input you’re waiting for and passively wait until that input is provided in order to execute your code. It’s a set and forget kind of deal, if you will.

What does the observer pattern look like?

Have you ever written your own HTTP server? Something like this:

const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Your own server here');
});
server.on('error', err => {
console.log(“Error:: “, err)
})
server.listen(3000, '127.0.0.1', () => {
console.log('Server up and running');
});
class Observable {    constructor() {
this.observers = {}
}
on(input, observer) {
if(!this.observers[input]) this.observers[input] = []
this.observers[input].push(observer)
}
triggerInput(input, params) {
this.observers[input].forEach( o => {
o.apply(null, params)
})
}
}
class Server extends Observable { constructor() {
super()
}
triggerError() {
let errorObj = {
errorCode: 500,
message: 'Port already in use'
}
this.triggerInput('error', [errorObj])
}
}
server.on('error', err => {
console.log(“Error:: “, err)
})
Error:: { errorCode: 500, message: 'Port already in use' }

Use cases

This pattern is, as you might have already guessed, great for dealing with asynchronous calls, since getting the response from an external request can be considered a new input. And what do we have in Node.js, if not a constant influx of asynchronous code into our projects? So next time you’re having to deal with an async scenario consider looking into this pattern.

Chain of responsibility

The chain of responsibility pattern is one that many of use in the world of Node.js have used, without even realizing it.

What does the chain of responsibility look like?

Here is a very basic implementation of this pattern, as you can see at the bottom, we have four possible values (or requests) that we need to process, but we don’t care who gets to process them, we just need, at least, one function to use them, hence we just send it to the chain and let each one decide whether they should use it or ignore it.

function processRequest(r, chain) {    let lastResult = null
let i = 0
do {
lastResult = chain[i](r)
i++
} while(lastResult != null && i < chain.length)
if(lastResult != null) {
console.log("Error: request could not be fulfilled")
}
}
let chain = [
function (r) {
if(typeof r == 'number') {
console.log("It's a number: ", r)
return null
}
return r
},
function (r) {
if(typeof r == 'string') {
console.log("It's a string: ", r)
return null
}
return r
},
function (r) {
if(Array.isArray(r)) {
console.log("It's an array of length: ", r.length)
return null
}
return r
}
]
processRequest(1, chain)
processRequest([1,2,3], chain)
processRequest('[1,2,3]', chain)
processRequest({}, chain)
It's a number:  1
It's an array of length: 3
It's a string: [1,2,3]
Error: request could not be fulfilled

Use cases

The most obvious case of this pattern in our ecosystem is the middlewares for ExpressJS. With that pattern, you’re essentially setting up a chain of functions (middlewares) that evaluate the request object and decide to act on it or ignore it. You can think of that pattern as the asynchronous version of the above example, where instead of checking if the function returns a value or not, you’re checking what values are passed to the next callback they call.

var app = express();app.use(function (req, res, next) {
console.log('Time:', Date.now());
next(); //call the next function on the chain
});

Final thoughts

These are but a few patterns that you might run into daily without even realizing it. I’d encourage you to look into the rest of them, even if you don’t find an immediate use case, now that I’ve shown you how some of them look in the wild, you might start seeing them yourselves! Hopefully, this article has shed some light on this subject and helps you improve your coding-foo faster than ever. See you on the next one!

The Startup

Medium's largest active publication, followed by +704K people. Follow to join our community.

Unlisted

Madhavan Nagarajan

Written by

passionate, self-motivated developer with a good experience in IT and enjoy playing around web technologies. believe in going an extra mile…

The Startup

Medium's largest active publication, followed by +704K people. Follow to join our community.

Madhavan Nagarajan

Written by

passionate, self-motivated developer with a good experience in IT and enjoy playing around web technologies. believe in going an extra mile…

The Startup

Medium's largest active publication, followed by +704K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store