Introduction to Node.js Design Patterns

Friday, January 05, 2024

Node JS design pattern is just like a blueprint of what can be the customized solution for the code’s issues. This means when the developers are facing issues in designing software, it can be possible that they have faced similar types of issues before, and at that time, they can take a look at the most common patterns to find a solution to a commonly occurring problem.

And such types of fundamental Node.js design patterns are very popular as they are just like a toolkit of optimized, tried, and tested solutions for common issues. This is why Node.js app development companies use design patterns like the singleton pattern and the factory pattern when they encounter these problems. In this blog, we will have a look at some of the most popular types of design patterns in Node.js that can help in solving design issues and improve communication in teams when such problems arise.

1. What are Design Patterns?

Design patterns in Node.js are practices that  empower  developers to structure their code solution’s in a manner that  provide benefits  such as code reusability and faster development. All these patterns are easily accessible while working with the object-oriented programming (OOP) paradigm. Besides this, you can also use design patterns with non-OOP projects because of JavaScript’s flexibility in this approach.

2. Type of Design Patterns

Here are some of the most popular types of Node js design patterns –

2.1 The Singleton Pattern

The Singleton pattern is one of the most important creational design patterns, ensuring that an abstract class in the code only has one instance that offers a global point to the developers who want to access this instance for the entire application. Developers can use this Node.js design patterns to enable the shared resource’s utilization without the risk of creating multiple instances throughout the application.

For example, when an application implements a singleton pattern for a database connection, it ensures that it creates only one connection, even when the application accesses the database from various places.This not only reduces the overhead of establishing multiple connections in the application but also provides a central point to control the entire database connection.

Here is the example of the Singleton Class in the below code:

const databaseSingleton = (() => {
  let instance;

  const createDatabaseInstance = () => {
    const database = new Database();
    return database;
  };

  return {
    getInstance: () => {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    },
  };
})();

class Database {
  constructor() {
    console.log("Creating a new database connection");
  }
}

const firstDbInstance= databaseSingleton.getInstance();
const secondDbInstance = databaseSingleton.getInstance();

console.log(firstDbInstance === secondDbInstance); // Output: true

The above code shows the implementation of a singleton pattern with the assistance of the databaseSingleton variable, which is defined as a closure. Here, the closure returns an object with the help of the method called getInstance which ultimately returns the Database class’s single instance. When the getInstance method is first called in this situation, it creates a new instance of the Database class and stores it in the instance variable.

Furthermore, when you make subsequent calls to getInstance, it will return the existing instance, ultimately ensuring that you can easily access only one instance of the Database class from anywhere in the code.

Benefits of the Singleton Pattern

  • Simplified access and control
  • Unique entity model
  • Global access point

2.2 The Builder Pattern

The builder design pattern is another one of the most popular types of design patterns. This pattern enables developers to separate the construction of objects  from their representation. This approach enables the developers to simplify the code that is complex and also creates complex objects. To understand the builder design pattern more clearly, let us go through an example. Here is an example in which classes named Car and CarBuilder enable users to create a car.

class Car {
    constructor(make, model, year, isAvailable = true, isStocked = false) {
        this.make = make;
        this.model = model;
        this.year = year;
      this.isAvailable = isAvailable; this.isStocked = isStocked;
    }

    toString() {
        return console.log(JSON.stringify(this));
    }
}

class CarBuilder {
    constructor(make, model, year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    notForSale() {
        this.isAvailable = false;

        return this;
    }

    addInStock() {
        this.isStocked= true;

        return this;
    }

    build() {
        return new Car(this.make, this.model, this.year, this.isAvailable, this.isStocked);
    }
}

module.exports = CarBuilder;

Here, we will create cars with the use of the CarBuilder class and not the Car class. For this, the developer will have to follow the given code.

const CarBuilder = require('./CarBuilder');

const marutisuzuki = new CarBuilder('Maruti-Suzuki', 'Brezza', 2017).addInStock().build();
const hyundai = new CarBuilder('Hyundai', 'Creta', 2021).notForSale().build();
const mercedes = new CarBuilder('mercedes-benz', 'c-class', 2019).build();

As seen in the above code, when the developers use the Builder design pattern, it helps them in making complex objects that are less prone to errors as with this approach understanding which parameters do what becomes easier.

Besides this, if one wants to create cars without using the CarBuilder class, here is the code for it.

const volvo = new CarBuilder('volvo', 'xc19', 2023, true, true);

In the above code, you can see that the line is too confusing as one cannot figure out what the two boolean values (“true”) mean. In the same way, if the object is too complex then creating it might become more confusing and there are chances that the developers will introduce errors in the code and the program won’t run as required. The developers use the builder design pattern approach. It helps in separating the complex object by having creation and representation parts differently.

Benefits of the Builder Pattern

  • Creation of immutable objects without complex logic.
  • Minimizing the number of constructor parameters.
  • You can call highly relatable methods.

2.3 The Observer Pattern

The Observer pattern is also one of the popular types of Node.js patterns when it comes to creating applications with simple approaches. A behavioral design pattern known as a one-to-many dependency injection enables developers to define a dependency between the program’s objects. This implies that when an object in the code changes its state, it automatically notifies all dependent objects about the change, and they update accordingly. Using this pattern is beneficial when Node.js developers want to implement real-time updates, such as  push notifications in their programs.

To understand this concept clearly, let’s have a look at this example. An application for the stock exchange requires the implementation of an update using the Observer pattern whenever a stock price changes. In this case, when the stock price changes, the observer objects will get notified immediately and an automatic update will occur to ensure that the users get the latest data on time.

Let’s have a look at the practical example of the observer design pattern approach by going through this Observer Class example.

class EventObserver {
  constructor() {
    this.observers = [];
  }

  subscribe(fn) {
    this.observers.push(fn);
  }

  unsubscribe(fn) {
    this.observers = this.observers.filter((subscriber) => subscriber !== fn);
  }

  broadcast(data) {
    this.observers.forEach((subscriber) => subscriber(data));
  }
}

const observer = new EventObserver();

const observerOne = (data) => console.log(`Observer One: ${data}`);
const observerTwo = (data) => console.log(`Observer Two: ${data}`);

observer.subscribe(observerOne);
observer.subscribe(observerTwo);

observer.broadcast("Hello, Observers!");

In the above example, we observed  that the EventObserver class in  the program acts as the subject, and as observers, we have the observerOne and observerTwo functions.Here, the list of subscribers maintains the subject and also provides methods that users can use to add or remove subscribers. Besides this, it also helps in broadcasting the events to all the subscribers of the application. And when the broadcast method is called, it starts to iterate via the subscriber’s lists and calls every subscriber with the new data that is provided to it. In this way, the observer pattern can send real-time data around the application and to its users.

Benefits of the Observer Pattern

  • Support supports the loose coupling principle.
  • You can send data to other objects very efficiently.

2.4 The Factory Method Pattern

Another popular Node.js design pattern is the factory pattern. A creational design pattern known as a type of pattern is capable of creating objects without the need to specify the exact class of objects that must be created. This approach proves very helpful in Node.js when developers intend to create a program where they have to create the same object or it might get created at multiple places in the entire application.

With the help of the Factory design pattern, the Node.js developers can make a single object of the application act as a factory and that object will be then responsible for creating new objects. The object that acts like a factory can configure itself to return different types of objects based on the inputs it receives from the client side. This approach makes it easier for any application to change its object creation process without any requirement of modifying the code in various places.

Here is an example of the factory design patterns.

function vehicleFactory(type) {
  if (type === "car") {
    return new Car();
  } else if (type === "truck") {
    return new Truck();
  } else {
    throw new Error(`Unsupported vehicle type: ${type}`);
  }
}

class Car {
  drive() {
    console.log("Driving a car");
  }
}

class Truck {
  drive() {
    console.log("Driving a truck");
  }
}

const car = vehicleFactory("car");
car.drive(); // Output: Driving a car

const truck = vehicleFactory("truck");
truck.drive(); // Output: Driving a truck

As seen in the above code, there is a function named vehicleFactory that takes a type parameter which means that it specifies the type of vehicle that is to be created. Here the function will return an instance which as per the type of the value, the function will return an instance which can be either of the Truck or the Car class. Besides this, the factory function here will act as a central point that can easily control the creation of all the objects and make it easy for the developers to add or remove any type of vehicle in the application when required.

Benefits of the Factory Pattern

  • Sub-classes can choose the types of objects that need to be created.
  • Supporting loose coupling eliminates the binding of app-specific classes.

2.5 The Prototype Pattern

The last Node.js design pattern in our list is the prototype pattern. One must know that JavaScript is a very popular prototype-based language before understanding it. This language makes use of prototypal inheritance, implying that each object in this approach has the capability to inherit from other objects. Developers can, therefore, use the prototype design patterns to create objects by cloning the value of the prototype, known as a sample object. This pattern ensures that the prototype acts as the blueprint for all the new objects created for an application when developers use it.

The prototype design pattern offers one of the most significant benefits, as it involves developing functions in objects by referencing other objects. This implies that, in this approach, all created objects point to the same function instead of having different functions or creating copies of one function. When developing a Node.js app using this approach, all objects inherited from the prototype gain access to the prototype’s functions.

Here is an example that explains this approach very clearly.

const atv = {
    make: ''Yamaha'',
    model: 'Foreman 500',
    year: 2018,
    mud: () => {
        console.log('Mudding');
    }
};

const secondATV = Object.create(atv);

As seen in the above code, in order to create a new object from the same prototype, the developers have used the Object.create() method. And here the second object, secondATV that is created also holds the same value as the first one which is – atv. To ensure that the program works correctly, the mud function is called which helps in printing the property of any object.

Now, there is also another way to use the Prototype Design pattern in your application code which is by defining the prototype in the “class”. Here we will see an example of the same.

const atvPrototype = {
    mud: () => {
        console.log('Mudding');
    }
};

// Atv constructor function
function Atv(make, model, year) {
        this.make = make;
        this.model = model;
        this.year = year;
};

Atv.prototype = atvPrototype;

// Create Atv instances
const atv1 = new Atv('Yamaha','Neo Plus ATV','2021');
const atv2 = new Atv('Honda', 'Rincon 650', '2018');

In the above code, both instances that are specified will access whatever is defined in the atvPrototype object.Besides, these instances will point to the same function without calling the mud function. Both these code examples prove that the Prototype design pattern is useful and it can help the developers have objects that share the same properties or functions for any application.

Benefits of the Prototype Pattern

  • Enables the hiding of complexities that occurs while creating objects.
  • Helps in reducing the requirement of sub-classing.
  • Allows to add or remove objects easily during runtime.

3. Conclusion

Here, we saw that the design patterns in Node.js come with their own unique set of benefits, elements, and purposes which can be beneficial to Node.js developers to create applications with ease. Besides, these patterns come with scalability, quality, flexibility, and maintainability of the code which ultimately helps the developers to create code that is robust for the module system of any application. This is why an understanding of these patterns and other asynchronous control flow patterns of Node.js can help be very essential for experts for creating objects and making the functions of enterprise-grade distributed applications run smoothly. 

4. Frequently Asked Questions

What design pattern is used in node JS?

There are many design patterns available in NodeJS. Some of the prevalent ones are as follows:

  • The factory method pattern
  • The singleton pattern
  • The builder pattern
  • The prototype pattern
  • The observer pattern

Are MVC and MVVM design patterns?

Yes, they both are design patterns but different from each other. MVC patterns are easy and helpful for separating out tasks, they lead to massive and intricate codebases. Whereas, direct interaction between models and views via MVVM patterns results in fast applications well-suited to dynamic data.

Comments


Your comment is awaiting moderation.