Design Patterns in Node.js
Design patterns are a set of reusable solutions to common software design problems. They are an essential tool for any developer to have in their toolkit, and Node.js developers are no exception. Node.js is a popular server-side runtime environment for JavaScript, and it offers a rich set of libraries and tools for building scalable, high-performance applications.
Module Pattern
The Module Pattern is a popular design pattern in Node.js that encapsulates functionality into a module that can be easily reused across the application. Modules are self-contained units of code that can be imported and exported to other parts of the application. The Module Pattern provides a way to structure code into smaller, more manageable pieces, making it easier to maintain and test.
// module.js
const privateVariable = 'I am private';
function privateFunction() {
console.log('I am private');
}
function publicFunction() {
console.log('I am public');
}
module.exports = {
publicFunction: publicFunction
};
// app.js
const module = require('./module');
module.publicFunction(); // outputs "I am public"
Singleton Pattern
The Singleton Pattern is a design pattern that restricts the instantiation of a class to a single instance and provides a global point of access to that instance. In Node.js, the Singleton Pattern is commonly used to create global objects that can be accessed by other parts of the application.
// singleton.js
class Singleton {
constructor() {
if (!Singleton.instance) {
this._data = [];
Singleton.instance = this;
}
return Singleton.instance;
}
getData() {
return this._data;
}
setData(data) {
this._data = data;
}
}
module.exports = Singleton;
// app.js
const Singleton = require('./singleton');
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // outputs "true"
instance1.setData([1, 2, 3]);
console.log(instance2.getData()); // outputs "[1, 2, 3]"
Factory Pattern
The Factory Pattern is a creational design pattern that provides an interface for creating objects, but allows subclasses to decide which classes to instantiate. In Node.js, the Factory Pattern is commonly used to create objects dynamically at runtime.
// factory.js
class ProductA {
constructor() {
this.name = 'Product A';
}
}
class ProductB {
constructor() {
this.name = 'Product B';
}
}
class ProductFactory {
createProduct(productType) {
switch (productType) {
case 'A':
return new ProductA();
case 'B':
return new ProductB();
default:
throw new Error(`Invalid product type: ${productType}`);
}
}
}
module.exports = ProductFactory;
// app.js
const ProductFactory = require('./factory');
const factory = new ProductFactory();
const productA = factory.createProduct('A');
console.log(productA.name); // outputs "Product A"
const productB = factory.createProduct('B');
console.log(productB.name); // outputs "Product B"
Middleware Pattern
The Middleware Pattern is a design pattern that provides a way to chain multiple functions together to handle a request. In Node.js, the Middleware Pattern is commonly used in web frameworks like Express to handle requests by chaining together functions that perform specific tasks.
// app.js
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Middleware 1');
next();
});
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
/*
When the / route is accessed, the output will be:
Middleware 1
Middleware 2
*/
Observer Pattern
The Observer Pattern is a design pattern that defines a one-to-many relationship between objects. When the state of one object changes, all of its dependents are notified and updated automatically. In Node.js, the Observer Pattern is commonly used to implement event-driven architectures by subscribing to events and reacting to them as they occur.
// observer.js
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor() {
this.data = null;
}
update(data) {
this.data = data;
console.log(`Data updated: ${this.data}`);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello, world!');
/*
The output will be:
Data updated: Hello, world!
Data updated: Hello, world!
*/
Promises Pattern
The Promises Pattern is a design pattern that provides a way to handle asynchronous operations in a more organized and readable way. Promises are objects that represent the eventual completion or failure of an asynchronous operation and provide a way to chain multiple asynchronous operations together.
// promise.js
function getData() {
return new Promise((resolve, reject) => {
// simulate an asynchronous operation
setTimeout(() => {
const data = [1, 2, 3];
if (data) {
resolve(data);
} else {
reject(new Error('Failed to get data'));
}
}, 1000);
});
}
getData()
.then(data => {
console.log(data); // outputs "[1, 2, 3]"
})
.catch(error => {
console.error(error);
});
/*
When getData() is called,
it returns a promise that resolves with [1, 2, 3]
after a simulated asynchronous operation.
The .then() method is used to handle the resolved value,
and the .catch() method is used to handle any errors that may occur.
*/
Dependency Injection Pattern
The Dependency Injection Pattern is a design pattern that provides a way to inject dependencies into an object or function rather than creating them internally. In Node.js, the Dependency Injection Pattern is commonly used to inject dependencies into controllers or services, making them more modular and easier to test.
// dependency-injection.js
class Database {
constructor(connectionString) {
this.connectionString = connectionString;
}
connect() {
console.log(`Connecting to ${this.connectionString}`);
}
disconnect() {
console.log(`Disconnecting from ${this.connectionString}`);
}
}
class UserService {
constructor(database) {
this.database = database;
}
register(user) {
console.log(`Registering user: ${user}`);
this.database.connect();
// perform registration logic
this.database.disconnect();
}
}
const database = new Database('mongodb://localhost/mydb');
const userService = new UserService(database);
userService.register('John Doe');
+---------------------------+
| Node.js App |
+---------------------------+
|
+-----------------------------------------+
| | |
v v v
+------------------+ +------------------+ +------------------+
| Module | | Factory | | Observer |
| Pattern | | Pattern | | Pattern |
+------------------+ +------------------+ +------------------+
| | |
v v v
+------------------+ +------------------+ +------------------+
| Middleware | | Dependency | | Promises/Async |
| Pattern | | Injection | | Await |
+------------------+ +------------------+ +------------------+
| | |
v v v
+------------------+ +------------------+ +------------------+
| MVC | | Repository | | Singleton |
| Pattern | | Pattern | | Pattern |
+------------------+ +------------------+ +------------------+