SOLID Principles in JavaScript: Advanced Implementation and Code Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: SOLID in the JavaScript Ecosystem

SOLID is an acronym for five fundamental principles of object-oriented design formulated by Robert Martin. In the context of JavaScript, especially when developing on Node.js using TypeScript and modern frameworks (NestJS, Express), these principles become critically important for building scalable and maintainable applications. At an advanced level, SOLID goes beyond simple classes and interfaces, touching on module architecture, dependency injection, and testing.

In this article, we will examine each SOLID principle with code examples for Node.js, focusing on practical nuances and typical mistakes. You will learn how to apply SOLID in real projects using the capabilities of JavaScript (ES6+) and TypeScript.



1. Single Responsibility Principle (SRP) in JavaScript

Theory: A class or module should have only one reason to change. In JavaScript, this is often violated when business logic, database operations, and response formatting are mixed in one file.

Practice (Node.js): Separate responsibilities at the level of services and repositories.

// ❌ SRP Violation: class handles requests, validates, and savesclass UserService {    createUser(data) {        if (!data.email) throw new Error('Email required');        const db = new Database();        return db.save('users', data);    }}

// ✅ SRP Compliance: separation into validator, repository, and serviceclass UserValidator { validate(data) { if (!data.email) throw new Error('Email required'); }}

class UserRepository { constructor(db) { this.db = db; } save(data) { return this.db.save('users', data); }}

class UserService { constructor(validator, repository) { this.validator = validator; this.repository = repository; } createUser(data) { this.validator.validate(data); return this.repository.save(data); }}

Use dependency injection (DI) to pass the validator and repository into the service. This simplifies testing and component replacement.



2. Open/Closed Principle (OCP) through Polymorphism

Theory: Classes should be open for extension but closed for modification. In JavaScript, this is achieved through inheritance, composition, or strategies.

Practice (Node.js with TypeScript): Use abstract classes or interfaces to define a contract.

// Abstract class for reportsabstract class ReportGenerator {    abstract generate(data: any): string;}

class PDFReport extends ReportGenerator { generate(data: any): string { return `PDF: ${JSON.stringify(data)}`; }}

class CSVReport extends ReportGenerator { generate(data: any): string { return `CSV: ${data.join(',')}`; }}

// Client code does not change when adding new formatsclass ReportService { constructor(private generator: ReportGenerator) {} createReport(data: any) { return this.generator.generate(data); }}

For pure JavaScript, use factories or strategies (Strategy pattern) to avoid modifying existing code.



3. Liskov Substitution Principle (LSP) in Asynchronous Code

Theory: Objects of a base class should be replaceable with objects of a derived class without breaking the program. In JavaScript, LSP is often violated when overriding methods with different contracts (e.g., return types or exceptions).

Practice (Node.js with async/await): Ensure that subclasses do not weaken preconditions or strengthen postconditions.

class Database {    async connect() { return 'con                

Blogs

Book Recommendations