Understanding SOLID Principles in JavaScript and Node.js

Pawan Kumar
3 min readNov 18, 2023

SOLID is a set of principles in object-oriented programming that aims to create scalable, maintainable, and flexible software. These principles guide developers in designing clean and efficient code. Let’s explore each SOLID principle with examples in JavaScript and Node.js.

1. Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change. In other words, it should have only one responsibility. Let’s consider a simple example:

// Without SRP
class User {
constructor(name) {
this.name = name;
}

saveToDatabase() {
// Save user to the database
}

sendEmail() {
// Send welcome email to the user
}
}

// With SRP
class User {
constructor(name) {
this.name = name;
}
}

class UserRepository {
saveToDatabase(user) {
// Save user to the database
}
}

class EmailService {
sendWelcomeEmail(user) {
// Send welcome email to the user
}
}

By separating concerns, each class now has a single responsibility: managing user data, saving to the database, and sending emails.

2. Open/Closed Principle (OCP)

The Open/Closed Principle states that software entities should be open for extension but closed for modification. This encourages using abstraction and interfaces. Here’s an example:

// Without OCP
class Square {
constructor(side) {
this.side = side;
}
}

class AreaCalculator {
calculateSquareArea(square) {
return square.side * square.side;
}
}

// With OCP
class Shape {
calculateArea() {}
}

class Square extends Shape {
constructor(side) {
super();
this.side = side;
}

calculateArea() {
return this.side * this.side;
}
}

class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}

calculateArea() {
return Math.PI * this.radius * this.radius;
}
}

Now, you can easily add new shapes without modifying the AreaCalculator class.

3. Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. Consider the following example:

// Without LSP
class Bird {
fly() {
// Fly logic
}
}

class Penguin extends Bird {
// Penguins can't fly
fly() {
throw new Error('Penguins can\'t fly');
}
}

// With LSP
class Bird {
move() {}
}

class FlyingBird extends Bird {
fly() {
// Fly logic
}
}

class Penguin extends Bird {
// Penguins can't fly
move() {}
}

By adhering to LSP, you ensure that subclasses can be used interchangeably with their base class.

4. Interface Segregation Principle (ISP)

The Interface Segregation Principle states that a class should not be forced to implement interfaces it does not use. In JavaScript, there are no strict interfaces, but we can create similar structures:

// Without ISP
class Worker {
work() {
// Work logic
}

eat() {
// Eat logic
}
}

// With ISP
class Workable {
work() {}
}

class Eatable {
eat() {}
}

class Worker implements Workable, Eatable {
work() {
// Work logic
}

eat() {
// Eat logic
}
}

By breaking interfaces into smaller ones, classes can implement only what they need.

5. Dependency Inversion Principle (DIP)

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Consider this example:

// Without DIP
class LightBulb {
turnOn() {
// Turn on logic
}

turnOff() {
// Turn off logic
}
}

class Switch {
constructor(bulb) {
this.bulb = bulb;
}

operate() {
// Operate the bulb
if (/* some condition */) {
this.bulb.turnOn();
} else {
this.bulb.turnOff();
}
}
}

// With DIP
class Switchable {
turnOn() {}

turnOff() {}
}

class LightBulb implements Switchable {
turnOn() {
// Turn on logic
}

turnOff() {
// Turn off logic
}
}

class Switch {
constructor(device) {
this.device = device;
}

operate() {
// Operate the device
if (/* some condition */) {
this.device.turnOn();
} else {
this.device.turnOff();
}
}
}

Now, the Switch class depends on an abstraction (Switchable) rather than a concrete implementation.

In conclusion, applying SOLID principles in JavaScript and Node.js leads to more modular, scalable, and maintainable code. By understanding and implementing these principles, developers can create robust and flexible software architectures.

In conclusion, applying SOLID principles in JavaScript and Node.js leads to more modular, scalable, and maintainable code. By understanding and implementing these principles, developers can create robust and flexible software architectures.

--

--